diff options
Diffstat (limited to 'src/mscorlib/src/System/IO')
47 files changed, 22999 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/IO/BinaryReader.cs b/src/mscorlib/src/System/IO/BinaryReader.cs new file mode 100644 index 0000000000..8accf0bd77 --- /dev/null +++ b/src/mscorlib/src/System/IO/BinaryReader.cs @@ -0,0 +1,601 @@ +// 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. +** +** +============================================================*/ +namespace System.IO { + + using System; + using System.Runtime; + using System.Text; + using System.Globalization; + using System.Diagnostics.Contracts; + using System.Security; + +[System.Runtime.InteropServices.ComVisible(true)] + public class BinaryReader : IDisposable + { + private const int MaxCharBytesSize = 128; + + private Stream m_stream; + private byte[] m_buffer; + private Decoder m_decoder; + private byte[] m_charBytes; + private char[] m_singleChar; + private char[] m_charBuffer; + private int m_maxCharsSize; // From MaxCharBytesSize & Encoding + + // Performance optimization for Read() w/ Unicode. Speeds us up by ~40% + private bool m_2BytesPerChar; + private bool m_isMemoryStream; // "do we sit on MemoryStream?" for Read/ReadInt32 perf + private bool m_leaveOpen; + + public BinaryReader(Stream input) : this(input, new UTF8Encoding(), 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("input"); + } + if (encoding==null) { + throw new ArgumentNullException("encoding"); + } + if (!input.CanRead) + throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotReadable")); + Contract.EndContractBlock(); + m_stream = input; + m_decoder = encoding.GetDecoder(); + m_maxCharsSize = encoding.GetMaxCharCount(MaxCharBytesSize); + int minBufferSize = encoding.GetMaxByteCount(1); // max bytes per one char + if (minBufferSize < 16) + minBufferSize = 16; + m_buffer = new byte[minBufferSize]; + // m_charBuffer and m_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. + m_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 + m_isMemoryStream = (m_stream.GetType() == typeof(MemoryStream)); + m_leaveOpen = leaveOpen; + + Contract.Assert(m_decoder!=null, "[BinaryReader.ctor]m_decoder!=null"); + } + + public virtual Stream BaseStream { + get { + return m_stream; + } + } + + public virtual void Close() { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) { + if (disposing) { + Stream copyOfStream = m_stream; + m_stream = null; + if (copyOfStream != null && !m_leaveOpen) + copyOfStream.Close(); + } + m_stream = null; + m_buffer = null; + m_decoder = null; + m_charBytes = null; + m_singleChar = null; + m_charBuffer = null; + } + + public void Dispose() + { + Dispose(true); + } + + public virtual int PeekChar() { + Contract.Ensures(Contract.Result<int>() >= -1); + + if (m_stream==null) __Error.FileNotOpen(); + + if (!m_stream.CanSeek) + return -1; + long origPos = m_stream.Position; + int ch = Read(); + m_stream.Position = origPos; + return ch; + } + + public virtual int Read() { + Contract.Ensures(Contract.Result<int>() >= -1); + + if (m_stream==null) { + __Error.FileNotOpen(); + } + return InternalReadOneChar(); + } + + public virtual bool ReadBoolean(){ + FillBuffer(1); + return (m_buffer[0]!=0); + } + + public virtual byte ReadByte() { + // Inlined to avoid some method call overhead with FillBuffer. + if (m_stream==null) __Error.FileNotOpen(); + + int b = m_stream.ReadByte(); + if (b == -1) + __Error.EndOfFile(); + return (byte) b; + } + + [CLSCompliant(false)] + public virtual sbyte ReadSByte() { + FillBuffer(1); + return (sbyte)(m_buffer[0]); + } + + public virtual char ReadChar() { + int value = Read(); + if (value==-1) { + __Error.EndOfFile(); + } + return (char)value; + } + + public virtual short ReadInt16() { + FillBuffer(2); + return (short)(m_buffer[0] | m_buffer[1] << 8); + } + + [CLSCompliant(false)] + public virtual ushort ReadUInt16(){ + FillBuffer(2); + return (ushort)(m_buffer[0] | m_buffer[1] << 8); + } + + public virtual int ReadInt32() { + if (m_isMemoryStream) { + if (m_stream==null) __Error.FileNotOpen(); + // read directly from MemoryStream buffer + MemoryStream mStream = m_stream as MemoryStream; + Contract.Assert(mStream != null, "m_stream as MemoryStream != null"); + + return mStream.InternalReadInt32(); + } + else + { + FillBuffer(4); + return (int)(m_buffer[0] | m_buffer[1] << 8 | m_buffer[2] << 16 | m_buffer[3] << 24); + } + } + + [CLSCompliant(false)] + public virtual uint ReadUInt32() { + FillBuffer(4); + return (uint)(m_buffer[0] | m_buffer[1] << 8 | m_buffer[2] << 16 | m_buffer[3] << 24); + } + + public virtual long ReadInt64() { + FillBuffer(8); + uint lo = (uint)(m_buffer[0] | m_buffer[1] << 8 | + m_buffer[2] << 16 | m_buffer[3] << 24); + uint hi = (uint)(m_buffer[4] | m_buffer[5] << 8 | + m_buffer[6] << 16 | m_buffer[7] << 24); + return (long) ((ulong)hi) << 32 | lo; + } + + [CLSCompliant(false)] + public virtual ulong ReadUInt64() { + FillBuffer(8); + uint lo = (uint)(m_buffer[0] | m_buffer[1] << 8 | + m_buffer[2] << 16 | m_buffer[3] << 24); + uint hi = (uint)(m_buffer[4] | m_buffer[5] << 8 | + m_buffer[6] << 16 | m_buffer[7] << 24); + return ((ulong)hi) << 32 | lo; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public virtual unsafe float ReadSingle() { + FillBuffer(4); + uint tmpBuffer = (uint)(m_buffer[0] | m_buffer[1] << 8 | m_buffer[2] << 16 | m_buffer[3] << 24); + return *((float*)&tmpBuffer); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public virtual unsafe double ReadDouble() { + FillBuffer(8); + uint lo = (uint)(m_buffer[0] | m_buffer[1] << 8 | + m_buffer[2] << 16 | m_buffer[3] << 24); + uint hi = (uint)(m_buffer[4] | m_buffer[5] << 8 | + m_buffer[6] << 16 | m_buffer[7] << 24); + + ulong tmpBuffer = ((ulong)hi) << 32 | lo; + return *((double*)&tmpBuffer); + } + + public virtual decimal ReadDecimal() { + FillBuffer(16); + try { + return Decimal.ToDecimal(m_buffer); + } + catch (ArgumentException e) { + // ReadDecimal cannot leak out ArgumentException + throw new IOException(Environment.GetResourceString("Arg_DecBitCtor"), e); + } + } + + public virtual String ReadString() { + Contract.Ensures(Contract.Result<String>() != null); + + if (m_stream == null) + __Error.FileNotOpen(); + + 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(Environment.GetResourceString("IO.IO_InvalidStringLen_Len", stringLength)); + } + + if (stringLength==0) { + return String.Empty; + } + + if (m_charBytes==null) { + m_charBytes = new byte[MaxCharBytesSize]; + } + + if (m_charBuffer == null) { + m_charBuffer = new char[m_maxCharsSize]; + } + + StringBuilder sb = null; + do + { + readLength = ((stringLength - currPos)>MaxCharBytesSize)?MaxCharBytesSize:(stringLength - currPos); + + n = m_stream.Read(m_charBytes, 0, readLength); + if (n==0) { + __Error.EndOfFile(); + } + + charsRead = m_decoder.GetChars(m_charBytes, 0, n, m_charBuffer, 0); + + if (currPos == 0 && n == stringLength) + return new String(m_charBuffer, 0, charsRead); + + if (sb == null) + sb = StringBuilderCache.Acquire(stringLength); // Actual string length in chars may be smaller. + sb.Append(m_charBuffer, 0, charsRead); + currPos +=n; + + } while (currPos<stringLength); + + return StringBuilderCache.GetStringAndRelease(sb); + } + + [SecuritySafeCritical] + public virtual int Read(char[] buffer, int index, int count) { + if (buffer==null) { + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + } + if (index < 0) { + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (count < 0) { + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (buffer.Length - index < count) { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + } + Contract.Ensures(Contract.Result<int>() >= 0); + Contract.Ensures(Contract.Result<int>() <= count); + Contract.EndContractBlock(); + + if (m_stream==null) + __Error.FileNotOpen(); + + // SafeCritical: index and count have already been verified to be a valid range for the buffer + return InternalReadChars(buffer, index, count); + } + + [SecurityCritical] + private int InternalReadChars(char[] buffer, int index, int count) { + Contract.Requires(buffer != null); + Contract.Requires(index >= 0 && count >= 0); + Contract.Assert(m_stream != null); + + int numBytes = 0; + int charsRemaining = count; + + if (m_charBytes==null) { + m_charBytes = new byte[MaxCharBytesSize]; + } + + while (charsRemaining > 0) { + int 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. + numBytes = charsRemaining; + + // special case for DecoderNLS subclasses when there is a hanging byte from the previous loop + DecoderNLS decoder = m_decoder as DecoderNLS; + if (decoder != null && decoder.HasState && numBytes > 1) { + numBytes -= 1; + } + + if (m_2BytesPerChar) + numBytes <<= 1; + if (numBytes > MaxCharBytesSize) + numBytes = MaxCharBytesSize; + + int position = 0; + byte[] byteBuffer = null; + if (m_isMemoryStream) + { + MemoryStream mStream = m_stream as MemoryStream; + Contract.Assert(mStream != null, "m_stream as MemoryStream != null"); + + position = mStream.InternalGetPosition(); + numBytes = mStream.InternalEmulateRead(numBytes); + byteBuffer = mStream.InternalGetBuffer(); + } + else + { + numBytes = m_stream.Read(m_charBytes, 0, numBytes); + byteBuffer = m_charBytes; + } + + if (numBytes == 0) { + return (count - charsRemaining); + } + + Contract.Assert(byteBuffer != null, "expected byteBuffer to be non-null"); + + checked + { + if (position < 0 || numBytes < 0 || position > byteBuffer.Length - numBytes) + { + throw new ArgumentOutOfRangeException(nameof(numBytes)); + } + if (index < 0 || charsRemaining < 0 || index > buffer.Length - charsRemaining) + { + throw new ArgumentOutOfRangeException(nameof(charsRemaining)); + } + unsafe + { + fixed (byte* pBytes = byteBuffer) + fixed (char* pChars = buffer) + { + charsRead = m_decoder.GetChars(pBytes + position, numBytes, pChars + index, charsRemaining, flush: false); + } + } + } + + charsRemaining -= charsRead; + index+=charsRead; + } + + // this should never fail + Contract.Assert(charsRemaining >= 0, "We read too many characters."); + + // 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 (count - charsRemaining); + } + + private int InternalReadOneChar() { + // I know having a separate InternalReadOneChar method seems a little + // redundant, but this makes a scenario like the security parser code + // 20% faster, in addition to the optimizations for UnicodeEncoding I + // put in InternalReadChars. + int charsRead = 0; + int numBytes = 0; + long posSav = posSav = 0; + + if (m_stream.CanSeek) + posSav = m_stream.Position; + + if (m_charBytes==null) { + m_charBytes = new byte[MaxCharBytesSize]; + } + if (m_singleChar==null) { + m_singleChar = new 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 m_2BytesPerChar is true. + numBytes = m_2BytesPerChar ? 2 : 1; + + int r = m_stream.ReadByte(); + m_charBytes[0] = (byte) r; + if (r == -1) + numBytes = 0; + if (numBytes == 2) { + r = m_stream.ReadByte(); + m_charBytes[1] = (byte) r; + if (r == -1) + numBytes = 1; + } + + if (numBytes==0) { + // Console.WriteLine("Found no bytes. We're outta here."); + return -1; + } + + Contract.Assert(numBytes == 1 || numBytes == 2, "BinaryReader::InternalReadOneChar assumes it's reading one or 2 bytes only."); + + try { + + charsRead = m_decoder.GetChars(m_charBytes, 0, numBytes, m_singleChar, 0); + } + catch + { + // Handle surrogate char + + if (m_stream.CanSeek) + m_stream.Seek((posSav - m_stream.Position), SeekOrigin.Current); + // else - we can't do much here + + throw; + } + + Contract.Assert(charsRead < 2, "InternalReadOneChar - assuming we only got 0 or 1 char, not 2!"); + // Console.WriteLine("That became: " + charsRead + " characters."); + } + if (charsRead == 0) + return -1; + return m_singleChar[0]; + } + + [SecuritySafeCritical] + public virtual char[] ReadChars(int count) { + if (count<0) { + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + Contract.Ensures(Contract.Result<char[]>() != null); + Contract.Ensures(Contract.Result<char[]>().Length <= count); + Contract.EndContractBlock(); + if (m_stream == null) { + __Error.FileNotOpen(); + } + + if (count == 0) { + return EmptyArray<Char>.Value; + } + + // SafeCritical: we own the chars buffer, and therefore can guarantee that the index and count are valid + char[] chars = new char[count]; + int n = InternalReadChars(chars, 0, count); + if (n!=count) { + char[] copy = new char[n]; + Buffer.InternalBlockCopy(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("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.Ensures(Contract.Result<int>() >= 0); + Contract.Ensures(Contract.Result<int>() <= count); + Contract.EndContractBlock(); + + if (m_stream==null) __Error.FileNotOpen(); + return m_stream.Read(buffer, index, count); + } + + public virtual byte[] ReadBytes(int count) { + if (count < 0) throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.Ensures(Contract.Result<byte[]>() != null); + Contract.Ensures(Contract.Result<byte[]>().Length <= Contract.OldValue(count)); + Contract.EndContractBlock(); + if (m_stream==null) __Error.FileNotOpen(); + + if (count == 0) { + return EmptyArray<Byte>.Value; + } + + byte[] result = new byte[count]; + + int numRead = 0; + do { + int n = m_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.InternalBlockCopy(result, 0, copy, 0, numRead); + result = copy; + } + + return result; + } + + protected virtual void FillBuffer(int numBytes) { + if (m_buffer != null && (numBytes < 0 || numBytes > m_buffer.Length)) { + throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_BinaryReaderFillBuffer")); + } + int bytesRead=0; + int n = 0; + + if (m_stream==null) __Error.FileNotOpen(); + + // 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 = m_stream.ReadByte(); + if (n==-1) + __Error.EndOfFile(); + m_buffer[0] = (byte)n; + return; + } + + do { + n = m_stream.Read(m_buffer, bytesRead, numBytes-bytesRead); + if (n==0) { + __Error.EndOfFile(); + } + bytesRead+=n; + } while (bytesRead<numBytes); + } + + internal protected 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(Environment.GetResourceString("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/src/mscorlib/src/System/IO/BinaryWriter.cs b/src/mscorlib/src/System/IO/BinaryWriter.cs new file mode 100644 index 0000000000..c775cbc9ff --- /dev/null +++ b/src/mscorlib/src/System/IO/BinaryWriter.cs @@ -0,0 +1,427 @@ +// 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 way to write primitives types in +** binary from a Stream, while also supporting writing Strings +** in a particular encoding. +** +** +===========================================================*/ +using System; +using System.Runtime; +using System.Runtime.Serialization; +using System.Text; +using System.Diagnostics.Contracts; + +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. + // + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + public class BinaryWriter : IDisposable + { + public static readonly BinaryWriter Null = new BinaryWriter(); + + protected Stream OutStream; + private byte[] _buffer; // temp space for writing primitives to. + private Encoding _encoding; + private Encoder _encoder; + + [OptionalField] // New in .NET FX 4.5. False is the right default value. + private bool _leaveOpen; + + // This field should never have been serialized and has not been used since before v2.0. + // However, this type is serializable, and we need to keep the field name around when deserializing. + // Also, we'll make .NET FX 4.5 not break if it's missing. +#pragma warning disable 169 + [OptionalField] + private char[] _tmpOneCharBuffer; +#pragma warning restore 169 + + // 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 = new UTF8Encoding(false, true); + _encoder = _encoding.GetEncoder(); + } + + public BinaryWriter(Stream output) : this(output, new UTF8Encoding(false, true), 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("output"); + if (encoding==null) + throw new ArgumentNullException("encoding"); + if (!output.CanWrite) + throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotWritable")); + Contract.EndContractBlock(); + + 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); + } + + /* + * Returns the stream associate 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("buffer"); + Contract.EndContractBlock(); + 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. + // + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual void Write(char ch) { + if (Char.IsSurrogate(ch)) + throw new ArgumentException(Environment.GetResourceString("Arg_SurrogatesNotAllowedAsSingleChar")); + Contract.EndContractBlock(); + + Contract.Assert(_encoding.GetMaxByteCount(1) <= 16, "_encoding.GetMaxByteCount(1) <= 16)"); + int numBytes = 0; + fixed(byte * pBytes = _buffer) { + 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("chars"); + Contract.EndContractBlock(); + + 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. + // + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual 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. + // + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual 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. + // + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe virtual void Write(String value) + { + if (value==null) + throw new ArgumentNullException("value"); + Contract.EndContractBlock(); + + int len = _encoding.GetByteCount(value); + Write7BitEncodedInt(len); + + if (_largeByteBuffer == null) { + _largeByteBuffer = new byte[LargeByteBufferSize]; + _maxChars = _largeByteBuffer.Length / _encoding.GetMaxByteCount(1); + } + + if (len <= _largeByteBuffer.Length) + { + //Contract.Assert(len == _encoding.GetBytes(chars, 0, chars.Length, _largeByteBuffer, 0), "encoding's GetByteCount & GetBytes gave different answers! encoding type: "+_encoding.GetType().Name); + _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) + { + byteLen = _encoder.GetBytes(pChars + charStart, charCount, pBytes, _largeByteBuffer.Length, charCount == numLeft); + } + } + } +#if _DEBUG + totalBytes += byteLen; + Contract.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 + Contract.Assert(totalBytes == len, "BinaryWriter::Write(String) - Didn't write out all the bytes!"); +#endif + } + } + + 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/src/mscorlib/src/System/IO/BufferedStream.cs b/src/mscorlib/src/System/IO/BufferedStream.cs new file mode 100644 index 0000000000..0c73b5c0f5 --- /dev/null +++ b/src/mscorlib/src/System/IO/BufferedStream.cs @@ -0,0 +1,1320 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** Purpose: A composable Stream that buffers reads & writes to the underlying stream. +** +** +===========================================================*/ +using System; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Collections.ObjectModel; +using System.Security; +using System.Threading.Tasks; + +namespace System.IO { + +/// <summary> +/// One of the design goals here is to prevent the buffer from getting in the way and slowing +/// down underlying stream accesses when it is not needed. If you always read & write for sizes +/// greater than the internal buffer size, then this class may not even allocate the internal buffer. +/// See a large comment in Write for the details of the write buffer heuristic. +/// +/// This class buffers reads & writes in a shared buffer. +/// (If you maintained two buffers separately, one operation would always trash the other buffer +/// anyways, so we might as well use one buffer.) +/// The assumption here is you will almost always be doing a series of reads or writes, but rarely +/// alternate between the two of them on the same stream. +/// +/// 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: +/// <![CDATA[ +/// * 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. +/// ]]> +/// This class will never cache more bytes than the max specified buffer size. +/// However, it may use a temporary buffer of up to twice the size in order to combine several IO operations on +/// the underlying stream into a single operation. This is because we assume that memory copies are significantly +/// faster than IO operations on the underlying stream (if this was not true, using buffering is never appropriate). +/// The max size of this "shadow" buffer is limited as to not allocate it on the LOH. +/// Shadowing is always transient. Even when using this technique, this class still guarantees that the number of +/// bytes cached (not yet written to the target stream or not yet consumed by the user) is never larger than the +/// actual specified buffer size. +/// </summary> +[ComVisible(true)] +public sealed class BufferedStream : Stream { + + + private const Int32 _DefaultBufferSize = 4096; + + + private Stream _stream; // Underlying stream. Close sets _stream to null. + + private Byte[] _buffer; // Shared read/write buffer. Alloc on first use. + + private readonly Int32 _bufferSize; // Length of internal buffer (not counting the shadow buffer). + + private Int32 _readPos; // Read pointer within shared buffer. + private Int32 _readLen; // Number of bytes read in buffer from _stream. + private Int32 _writePos; // Write pointer within shared buffer. + + private BeginEndAwaitableAdapter _beginEndAwaitable; // Used to be able to await a BeginXxx call and thus to share code + // between the APM and Async pattern implementations + + private Task<Int32> _lastSyncCompletedReadTask; // The last successful Task returned from ReadAsync + // (perf optimization for successive reads of the same size) + + + // Removing a private default constructor is a breaking change for the DataContractSerializer. + // Because this ctor was here previously we need to keep it around. + private BufferedStream() { } + + + public BufferedStream(Stream stream) + + : this(stream, _DefaultBufferSize) { + } + + + public BufferedStream(Stream stream, Int32 bufferSize) { + + if (stream == null) + throw new ArgumentNullException("stream"); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_MustBePositive", "bufferSize")); + + Contract.EndContractBlock(); + + BCLDebug.Perf(!(stream is FileStream), "FileStream is buffered - don't wrap it in a BufferedStream"); + BCLDebug.Perf(!(stream is MemoryStream), "MemoryStream shouldn't be wrapped in a BufferedStream!"); + BCLDebug.Perf(!(stream is BufferedStream), "BufferedStream shouldn't be wrapped in another BufferedStream!"); + + _stream = stream; + _bufferSize = bufferSize; + + // Allocate _buffer on its first use - it will not be used if all reads + // & writes are greater than or equal to buffer size. + + if (!_stream.CanRead && !_stream.CanWrite) + __Error.StreamIsClosed(); + } + + + private void EnsureNotClosed() { + + if (_stream == null) + __Error.StreamIsClosed(); + } + + + private void EnsureCanSeek() { + + Contract.Requires(_stream != null); + + if (!_stream.CanSeek) + __Error.SeekNotSupported(); + } + + + private void EnsureCanRead() { + + Contract.Requires(_stream != null); + + if (!_stream.CanRead) + __Error.ReadNotSupported(); + } + + + private void EnsureCanWrite() { + + Contract.Requires(_stream != null); + + if (!_stream.CanWrite) + __Error.WriteNotSupported(); + } + + + private void EnsureBeginEndAwaitableAllocated() { + // We support only a single ongoing async operation and enforce this with a semaphore, + // so singleton is fine and no need to worry about a race condition here. + if (_beginEndAwaitable == null) + _beginEndAwaitable = new BeginEndAwaitableAdapter(); + } + + + /// <summary><code>MaxShadowBufferSize</code> is chosed such that shadow buffers are not allocated on the Large Object Heap. + /// Currently, an object is allocated on the LOH if it is larger than 85000 bytes. See LARGE_OBJECT_SIZE in ndp\clr\src\vm\gc.h + /// We will go with exactly 80 KBytes, although this is somewhat arbitrary.</summary> + private const Int32 MaxShadowBufferSize = 81920; // Make sure not to get to the Large Object Heap. + private void EnsureShadowBufferAllocated() { + + Contract.Assert(_buffer != null); + Contract.Assert(_bufferSize > 0); + + // Already have shadow buffer? + if (_buffer.Length != _bufferSize || _bufferSize >= MaxShadowBufferSize) + return; + + Byte[] shadowBuffer = new Byte[Math.Min(_bufferSize + _bufferSize, MaxShadowBufferSize)]; + Buffer.InternalBlockCopy(_buffer, 0, shadowBuffer, 0, _writePos); + _buffer = shadowBuffer; + } + + + private void EnsureBufferAllocated() { + + Contract.Assert(_bufferSize > 0); + + // BufferedStream is not intended for multi-threaded use, so no worries about the get/set race conditions on _buffer. + if (_buffer == null) + _buffer = new Byte[_bufferSize]; + } + + + internal Stream UnderlyingStream { + [FriendAccessAllowed] + [Pure] + get { return _stream; } + } + + + internal Int32 BufferSize { + [FriendAccessAllowed] + [Pure] + get { return _bufferSize; } + } + + + public override bool CanRead { + [Pure] + get { return _stream != null && _stream.CanRead; } + } + + + public override bool CanWrite { + [Pure] + get { return _stream != null && _stream.CanWrite; } + } + + + public override bool CanSeek { + [Pure] + get { return _stream != null && _stream.CanSeek; } + } + + + public override Int64 Length { + get { + EnsureNotClosed(); + + if (_writePos > 0) + FlushWrite(); + + return _stream.Length; + } + } + + + public override Int64 Position { + get { + EnsureNotClosed(); + EnsureCanSeek(); + + Contract.Assert(! (_writePos > 0 && _readPos != _readLen), "Read and Write buffers cannot both have data in them at the same time."); + return _stream.Position + (_readPos - _readLen + _writePos); + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + + EnsureNotClosed(); + EnsureCanSeek(); + + if (_writePos > 0) + FlushWrite(); + + _readPos = 0; + _readLen = 0; + _stream.Seek(value, SeekOrigin.Begin); + } + } + + + protected override void Dispose(bool disposing) { + + try { + if (disposing && _stream != null) { + try { + Flush(); + } finally { + _stream.Close(); + } + } + } finally { + _stream = null; + _buffer = null; + _lastSyncCompletedReadTask = null; + + // Call base.Dispose(bool) to cleanup async IO resources + base.Dispose(disposing); + } + } + + + public override void Flush() { + + EnsureNotClosed(); + + // Has WRITE data in the buffer: + if (_writePos > 0) { + + FlushWrite(); + Contract.Assert(_writePos == 0 && _readPos == 0 && _readLen == 0); + return; + } + + // Has READ data in the buffer: + if (_readPos < _readLen) { + + // If the underlying stream is not seekable AND we have something in the read buffer, then FlushRead would throw. + // We can either throw away the buffer resulting in data loss (!) or ignore the Flush. + // (We cannot throw becasue it would be a breaking change.) We opt into ignoring the Flush in that situation. + if (!_stream.CanSeek) + return; + + FlushRead(); + + // User streams may have opted to throw from Flush if CanWrite is false (although the abstract Stream does not do so). + // However, if we do not forward the Flush to the underlying stream, we may have problems when chaining several streams. + // Let us make a best effort attempt: + if (_stream.CanWrite || _stream is BufferedStream) + _stream.Flush(); + + Contract.Assert(_writePos == 0 && _readPos == 0 && _readLen == 0); + return; + } + + // We had no data in the buffer, but we still need to tell the underlying stream to flush. + if (_stream.CanWrite || _stream is BufferedStream) + _stream.Flush(); + + _writePos = _readPos = _readLen = 0; + } + + public override Task FlushAsync(CancellationToken cancellationToken) { + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<Int32>(cancellationToken); + + EnsureNotClosed(); + + return FlushAsyncInternal(cancellationToken, this, _stream, _writePos, _readPos, _readLen); + } + + + private static async Task FlushAsyncInternal(CancellationToken cancellationToken, + BufferedStream _this, Stream stream, Int32 writePos, Int32 readPos, Int32 readLen) { + + // We bring instance fields down as local parameters to this async method becasue BufferedStream is derived from MarshalByRefObject. + // Field access would be from the async state machine i.e., not via the this pointer and would require runtime checking to see + // if we are talking to a remote object, which is currently very slow + + Contract.Assert(stream != null); + + SemaphoreSlim sem = _this.EnsureAsyncActiveSemaphoreInitialized(); + await sem.WaitAsync().ConfigureAwait(false); + try { + + if (writePos > 0) { + + await _this.FlushWriteAsync(cancellationToken).ConfigureAwait(false); + Contract.Assert(_this._writePos == 0 && _this._readPos == 0 && _this._readLen == 0); + return; + } + + if (readPos < readLen) { + + // If the underlying stream is not seekable AND we have something in the read buffer, then FlushRead would throw. + // We can either throw away the buffer resulting in date loss (!) or ignore the Flush. (We cannot throw becasue it + // would be a breaking change.) We opt into ignoring the Flush in that situation. + if (!stream.CanSeek) + return; + + _this.FlushRead(); // not async; it uses Seek, but there's no SeekAsync + + // User streams may have opted to throw from Flush if CanWrite is false (although the abstract Stream does not do so). + // However, if we do not forward the Flush to the underlying stream, we may have problems when chaining several streams. + // Let us make a best effort attempt: + if (stream.CanRead || stream is BufferedStream) + await stream.FlushAsync(cancellationToken).ConfigureAwait(false); + + Contract.Assert(_this._writePos == 0 && _this._readPos == 0 && _this._readLen == 0); + return; + } + + // We had no data in the buffer, but we still need to tell the underlying stream to flush. + if (stream.CanWrite || stream is BufferedStream) + await stream.FlushAsync(cancellationToken).ConfigureAwait(false); + + // There was nothing in the buffer: + Contract.Assert(_this._writePos == 0 && _this._readPos == _this._readLen); + + } finally { + sem.Release(); + } + } + + + // Reading is done in blocks, but someone could read 1 byte from the buffer then write. + // At that point, the underlying stream's pointer is out of sync with this stream's position. + // All write functions should call this function to ensure that the buffered data is not lost. + private void FlushRead() { + + Contract.Assert(_writePos == 0, "BufferedStream: Write buffer must be empty in FlushRead!"); + + if (_readPos - _readLen != 0) + _stream.Seek(_readPos - _readLen, SeekOrigin.Current); + + _readPos = 0; + _readLen = 0; + } + + + private void ClearReadBufferBeforeWrite() { + + // This is called by write methods to clear the read buffer. + + Contract.Assert(_readPos <= _readLen, "_readPos <= _readLen [" + _readPos +" <= " + _readLen + "]"); + + // No READ data in the buffer: + if (_readPos == _readLen) { + + _readPos = _readLen = 0; + return; + } + + // Must have READ data. + Contract.Assert(_readPos < _readLen); + + // If the underlying stream cannot seek, FlushRead would end up throwing NotSupported. + // However, since the user did not call a method that is intuitively expected to seek, a better message is in order. + // Ideally, we would throw an InvalidOperation here, but for backward compat we have to stick with NotSupported. + if (!_stream.CanSeek) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_CannotWriteToBufferedStreamIfReadBufferCannotBeFlushed")); + + FlushRead(); + } + + + private void FlushWrite() { + + Contract.Assert(_readPos == 0 && _readLen == 0, + "BufferedStream: Read buffer must be empty in FlushWrite!"); + Contract.Assert(_buffer != null && _bufferSize >= _writePos, + "BufferedStream: Write buffer must be allocated and write position must be in the bounds of the buffer in FlushWrite!"); + + _stream.Write(_buffer, 0, _writePos); + _writePos = 0; + _stream.Flush(); + } + + + private async Task FlushWriteAsync(CancellationToken cancellationToken) { + + Contract.Assert(_readPos == 0 && _readLen == 0, + "BufferedStream: Read buffer must be empty in FlushWrite!"); + Contract.Assert(_buffer != null && _bufferSize >= _writePos, + "BufferedStream: Write buffer must be allocated and write position must be in the bounds of the buffer in FlushWrite!"); + + await _stream.WriteAsync(_buffer, 0, _writePos, cancellationToken).ConfigureAwait(false); + _writePos = 0; + await _stream.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + + private Int32 ReadFromBuffer(Byte[] array, Int32 offset, Int32 count) { + + Int32 readBytes = _readLen - _readPos; + Contract.Assert(readBytes >= 0); + + if (readBytes == 0) + return 0; + + Contract.Assert(readBytes > 0); + + if (readBytes > count) + readBytes = count; + + Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, readBytes); + _readPos += readBytes; + + return readBytes; + } + + + private Int32 ReadFromBuffer(Byte[] array, Int32 offset, Int32 count, out Exception error) { + + try { + + error = null; + return ReadFromBuffer(array, offset, count); + + } catch (Exception ex) { + error = ex; + return 0; + } + } + + + public override int Read([In, Out] Byte[] array, Int32 offset, Int32 count) { + + if (array == null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + EnsureNotClosed(); + EnsureCanRead(); + + Int32 bytesFromBuffer = ReadFromBuffer(array, offset, count); + + // 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 and this code was used with redirected pipes for a + // process's standard output, this can lead to deadlocks involving two processes. + // BUT - this is a breaking change. + // So: If we could not read all bytes the user asked for from the buffer, we will try once from the underlying + // stream thus ensuring the same blocking behaviour as if the underlying stream was not wrapped in this BufferedStream. + if (bytesFromBuffer == count) + return bytesFromBuffer; + + Int32 alreadySatisfied = bytesFromBuffer; + if (bytesFromBuffer > 0) { + count -= bytesFromBuffer; + offset += bytesFromBuffer; + } + + // So the READ buffer is empty. + Contract.Assert(_readLen == _readPos); + _readPos = _readLen = 0; + + // If there was anything in the WRITE buffer, clear it. + if (_writePos > 0) + FlushWrite(); + + // If the requested read is larger than buffer size, avoid the buffer and still use a single read: + if (count >= _bufferSize) { + + return _stream.Read(array, offset, count) + alreadySatisfied; + } + + // Ok. We can fill the buffer: + EnsureBufferAllocated(); + _readLen = _stream.Read(_buffer, 0, _bufferSize); + + bytesFromBuffer = ReadFromBuffer(array, offset, count); + + // 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 stream, + // 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. Additionally, translating one read on the + // BufferedStream to more than one read on the underlying Stream may defeat the whole purpose of buffering of the + // underlying reads are significantly more expensive. + + return bytesFromBuffer + alreadySatisfied; + } + + + public override IAsyncResult BeginRead(Byte[] buffer, Int32 offset, Int32 count, AsyncCallback callback, Object state) { + + if (buffer == null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // Previous version incorrectly threw NotSupported instead of ObjectDisposed. We keep that behaviour for back-compat. + // EnsureNotClosed(); + if (_stream == null) __Error.ReadNotSupported(); + EnsureCanRead(); + + Int32 bytesFromBuffer = 0; + // Try to satisfy the request from the buffer synchronously. But still need a sem-lock in case that another + // Async IO Task accesses the buffer concurrently. If we fail to acquire the lock without waiting, make this + // an Async operation. + SemaphoreSlim sem = base.EnsureAsyncActiveSemaphoreInitialized(); + Task semaphoreLockTask = sem.WaitAsync(); + if (semaphoreLockTask.Status == TaskStatus.RanToCompletion) { + + bool completeSynchronously = true; + try { + + Exception error; + bytesFromBuffer = ReadFromBuffer(buffer, offset, count, out error); + + // If we satistied enough data from the buffer, we can complete synchronously. + // 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 and this code was used with redirected pipes for a + // process's standard output, this can lead to deadlocks involving two processes. + // BUT - this is a breaking change. + // So: If we could not read all bytes the user asked for from the buffer, we will try once from the underlying + // stream thus ensuring the same blocking behaviour as if the underlying stream was not wrapped in this BufferedStream. + completeSynchronously = (bytesFromBuffer == count || error != null); + + if (completeSynchronously) { + + SynchronousAsyncResult asyncResult = (error == null) + ? new SynchronousAsyncResult(bytesFromBuffer, state) + : new SynchronousAsyncResult(error, state, isWrite: false); + if (callback != null) + callback(asyncResult); + + return asyncResult; + } + } finally { + if (completeSynchronously) // if this is FALSE, we will be entering ReadFromUnderlyingStreamAsync and releasing there. + sem.Release(); + } + } + + // Delegate to the async implementation. + return BeginReadFromUnderlyingStream(buffer, offset + bytesFromBuffer, count - bytesFromBuffer, callback, state, + bytesFromBuffer, semaphoreLockTask); + } + + + private IAsyncResult BeginReadFromUnderlyingStream(Byte[] buffer, Int32 offset, Int32 count, AsyncCallback callback, Object state, + Int32 bytesAlreadySatisfied, Task semaphoreLockTask) { + + Task<Int32> readOp = ReadFromUnderlyingStreamAsync(buffer, offset, count, CancellationToken.None, + bytesAlreadySatisfied, semaphoreLockTask, useApmPattern: true); + return TaskToApm.Begin(readOp, callback, state); + } + + + public override Int32 EndRead(IAsyncResult asyncResult) { + + if (asyncResult == null) + throw new ArgumentNullException("asyncResult"); + Contract.Ensures(Contract.Result<Int32>() >= 0); + Contract.EndContractBlock(); + + var sAR = asyncResult as SynchronousAsyncResult; + if (sAR != null) + return SynchronousAsyncResult.EndRead(asyncResult); + return TaskToApm.End<Int32>(asyncResult); + } + + + private Task<Int32> LastSyncCompletedReadTask(Int32 val) { + + Task<Int32> t = _lastSyncCompletedReadTask; + Contract.Assert(t == null || t.Status == TaskStatus.RanToCompletion); + + if (t != null && t.Result == val) + return t; + + t = Task.FromResult<Int32>(val); + _lastSyncCompletedReadTask = t; + return t; + } + + + public override Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) { + + if (buffer == null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // Fast path check for cancellation already requested + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<Int32>(cancellationToken); + + EnsureNotClosed(); + EnsureCanRead(); + + Int32 bytesFromBuffer = 0; + // Try to satisfy the request from the buffer synchronously. But still need a sem-lock in case that another + // Async IO Task accesses the buffer concurrently. If we fail to acquire the lock without waiting, make this + // an Async operation. + SemaphoreSlim sem = base.EnsureAsyncActiveSemaphoreInitialized(); + Task semaphoreLockTask = sem.WaitAsync(); + if (semaphoreLockTask.Status == TaskStatus.RanToCompletion) { + + bool completeSynchronously = true; + try { + Exception error; + bytesFromBuffer = ReadFromBuffer(buffer, offset, count, out error); + + // If we satistied enough data from the buffer, we can complete synchronously. + // 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 and this code was used with redirected pipes for a + // process's standard output, this can lead to deadlocks involving two processes. + // BUT - this is a breaking change. + // So: If we could not read all bytes the user asked for from the buffer, we will try once from the underlying + // stream thus ensuring the same blocking behaviour as if the underlying stream was not wrapped in this BufferedStream. + completeSynchronously = (bytesFromBuffer == count || error != null); + + if (completeSynchronously) { + + return (error == null) + ? LastSyncCompletedReadTask(bytesFromBuffer) + : Task.FromException<Int32>(error); + } + } finally { + if (completeSynchronously) // if this is FALSE, we will be entering ReadFromUnderlyingStreamAsync and releasing there. + sem.Release(); + } + } + + // Delegate to the async implementation. + return ReadFromUnderlyingStreamAsync(buffer, offset + bytesFromBuffer, count - bytesFromBuffer, cancellationToken, + bytesFromBuffer, semaphoreLockTask, useApmPattern: false); + } + + + /// <summary>BufferedStream should be as thin a wrapper as possible. We want that ReadAsync delegates to + /// ReadAsync of the underlying _stream and that BeginRead delegates to BeginRead of the underlying stream, + /// rather than calling the base Stream which implements the one in terms of the other. This allows BufferedStream + /// to affect the semantics of the stream it wraps as little as possible. At the same time, we want to share as + /// much code between the APM and the Async pattern implementations as possible. This method is called by both with + /// a corresponding useApmPattern value. Recall that Task implements IAsyncResult.</summary> + /// <returns>-2 if _bufferSize was set to 0 while waiting on the semaphore; otherwise num of bytes read.</returns> + private async Task<Int32> ReadFromUnderlyingStreamAsync(Byte[] array, Int32 offset, Int32 count, + CancellationToken cancellationToken, + Int32 bytesAlreadySatisfied, + Task semaphoreLockTask, bool useApmPattern) { + + // Same conditions validated with exceptions in ReadAsync: + // (These should be Contract.Requires(..) but that method had some issues in async methods; using Assert(..) for now.) + Contract.Assert(array != null); + Contract.Assert(offset >= 0); + Contract.Assert(count >= 0); + Contract.Assert(array.Length - offset >= count); + Contract.Assert(_stream != null); + Contract.Assert(_stream.CanRead); + Contract.Assert(_bufferSize > 0); + Contract.Assert(semaphoreLockTask != null); + + // Employ async waiting based on the same synchronization used in BeginRead of the abstract Stream. + await semaphoreLockTask.ConfigureAwait(false); + try { + + // The buffer might have been changed by another async task while we were waiting on the semaphore. + // Check it now again. + Int32 bytesFromBuffer = ReadFromBuffer(array, offset, count); + if (bytesFromBuffer == count) + return bytesAlreadySatisfied + bytesFromBuffer; + + if (bytesFromBuffer > 0) { + count -= bytesFromBuffer; + offset += bytesFromBuffer; + bytesAlreadySatisfied += bytesFromBuffer; + } + + Contract.Assert(_readLen == _readPos); + _readPos = _readLen = 0; + + // If there was anything in the WRITE buffer, clear it. + if (_writePos > 0) + await FlushWriteAsync(cancellationToken).ConfigureAwait(false); // no Begin-End read version for Flush. Use Async. + + // If the requested read is larger than buffer size, avoid the buffer and still use a single read: + if (count >= _bufferSize) { + + if (useApmPattern) { + EnsureBeginEndAwaitableAllocated(); + _stream.BeginRead(array, offset, count, BeginEndAwaitableAdapter.Callback, _beginEndAwaitable); + return bytesAlreadySatisfied + _stream.EndRead(await _beginEndAwaitable); + } else { + return bytesAlreadySatisfied + await _stream.ReadAsync(array, offset, count, cancellationToken).ConfigureAwait(false); + } + } + + // Ok. We can fill the buffer: + EnsureBufferAllocated(); + if (useApmPattern) { + EnsureBeginEndAwaitableAllocated(); + _stream.BeginRead(_buffer, 0, _bufferSize, BeginEndAwaitableAdapter.Callback, _beginEndAwaitable); + _readLen = _stream.EndRead(await _beginEndAwaitable); + } else { + _readLen = await _stream.ReadAsync(_buffer, 0, _bufferSize, cancellationToken).ConfigureAwait(false); + } + + bytesFromBuffer = ReadFromBuffer(array, offset, count); + return bytesAlreadySatisfied + bytesFromBuffer; + + } finally { + SemaphoreSlim sem = base.EnsureAsyncActiveSemaphoreInitialized(); + sem.Release(); + } + } + + + public override Int32 ReadByte() { + + EnsureNotClosed(); + EnsureCanRead(); + + if (_readPos == _readLen) { + + if (_writePos > 0) + FlushWrite(); + + EnsureBufferAllocated(); + _readLen = _stream.Read(_buffer, 0, _bufferSize); + _readPos = 0; + } + + if (_readPos == _readLen) + return -1; + + Int32 b = _buffer[_readPos++]; + return b; + } + + + private void WriteToBuffer(Byte[] array, ref Int32 offset, ref Int32 count) { + + Int32 bytesToWrite = Math.Min(_bufferSize - _writePos, count); + + if (bytesToWrite <= 0) + return; + + EnsureBufferAllocated(); + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, bytesToWrite); + + _writePos += bytesToWrite; + count -= bytesToWrite; + offset += bytesToWrite; + } + + + private void WriteToBuffer(Byte[] array, ref Int32 offset, ref Int32 count, out Exception error) { + + try { + + error = null; + WriteToBuffer(array, ref offset, ref count); + + } catch (Exception ex) { + error = ex; + } + } + + + public override void Write(Byte[] array, Int32 offset, Int32 count) { + + if (array == null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + EnsureNotClosed(); + EnsureCanWrite(); + + if (_writePos == 0) + ClearReadBufferBeforeWrite(); + + #region Write algorithm comment + // We need to use the buffer, while avoiding unnecessary buffer usage / memory copies. + // We ASSUME that memory copies are much cheaper than writes to the underlying stream, so if an extra copy is + // guaranteed to reduce the number of writes, we prefer it. + // We pick a simple strategy that makes degenerate cases rare if our assumptions are right. + // + // For every write, we use a simple heuristic (below) to decide whether to use the buffer. + // The heuristic has the desirable property (*) that if the specified user data can fit into the currently available + // buffer space without filling it up completely, the heuristic will always tell us to use the buffer. It will also + // tell us to use the buffer in cases where the current write would fill the buffer, but the remaining data is small + // enough such that subsequent operations can use the buffer again. + // + // Algorithm: + // Determine whether or not to buffer according to the heuristic (below). + // If we decided to use the buffer: + // Copy as much user data as we can into the buffer. + // If we consumed all data: We are finished. + // Otherwise, write the buffer out. + // Copy the rest of user data into the now cleared buffer (no need to write out the buffer again as the heuristic + // will prevent it from being filled twice). + // If we decided not to use the buffer: + // Can the data already in the buffer and current user data be combines to a single write + // by allocating a "shadow" buffer of up to twice the size of _bufferSize (up to a limit to avoid LOH)? + // Yes, it can: + // Allocate a larger "shadow" buffer and ensure the buffered data is moved there. + // Copy user data to the shadow buffer. + // Write shadow buffer to the underlying stream in a single operation. + // No, it cannot (amount of data is still too large): + // Write out any data possibly in the buffer. + // Write out user data directly. + // + // Heuristic: + // If the subsequent write operation that follows the current write operation will result in a write to the + // underlying stream in case that we use the buffer in the current write, while it would not have if we avoided + // using the buffer in the current write (by writing current user data to the underlying stream directly), then we + // prefer to avoid using the buffer since the corresponding memory copy is wasted (it will not reduce the number + // of writes to the underlying stream, which is what we are optimising for). + // ASSUME that the next write will be for the same amount of bytes as the current write (most common case) and + // determine if it will cause a write to the underlying stream. If the next write is actually larger, our heuristic + // still yields the right behaviour, if the next write is actually smaller, we may making an unnecessary write to + // the underlying stream. However, this can only occur if the current write is larger than half the buffer size and + // we will recover after one iteration. + // We have: + // useBuffer = (_writePos + count + count < _bufferSize + _bufferSize) + // + // Example with _bufferSize = 20, _writePos = 6, count = 10: + // + // +---------------------------------------+---------------------------------------+ + // | current buffer | next iteration's "future" buffer | + // +---------------------------------------+---------------------------------------+ + // |0| | | | | | | | | |1| | | | | | | | | |2| | | | | | | | | |3| | | | | | | | | | + // |0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|7|8|9|0|1|2|3|4|5|6|7|8|9| + // +-----------+-------------------+-------------------+---------------------------+ + // | _writePos | current count | assumed next count|avail buff after next write| + // +-----------+-------------------+-------------------+---------------------------+ + // + // A nice property (*) of this heuristic is that it will always succeed if the user data completely fits into the + // available buffer, i.e. if count < (_bufferSize - _writePos). + #endregion Write algorithm comment + + Contract.Assert(_writePos < _bufferSize); + + Int32 totalUserBytes; + bool useBuffer; + checked { // We do not expect buffer sizes big enough for an overflow, but if it happens, lets fail early: + totalUserBytes = _writePos + count; + useBuffer = (totalUserBytes + count < (_bufferSize + _bufferSize)); + } + + if (useBuffer) { + + WriteToBuffer(array, ref offset, ref count); + + if (_writePos < _bufferSize) { + + Contract.Assert(count == 0); + return; + } + + Contract.Assert(count >= 0); + Contract.Assert(_writePos == _bufferSize); + Contract.Assert(_buffer != null); + + _stream.Write(_buffer, 0, _writePos); + _writePos = 0; + + WriteToBuffer(array, ref offset, ref count); + + Contract.Assert(count == 0); + Contract.Assert(_writePos < _bufferSize); + + } else { // if (!useBuffer) + + // Write out the buffer if necessary. + if (_writePos > 0) { + + Contract.Assert(_buffer != null); + Contract.Assert(totalUserBytes >= _bufferSize); + + // Try avoiding extra write to underlying stream by combining previously buffered data with current user data: + if (totalUserBytes <= (_bufferSize + _bufferSize) && totalUserBytes <= MaxShadowBufferSize) { + + EnsureShadowBufferAllocated(); + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, count); + _stream.Write(_buffer, 0, totalUserBytes); + _writePos = 0; + return; + } + + _stream.Write(_buffer, 0, _writePos); + _writePos = 0; + } + + // Write out user data. + _stream.Write(array, offset, count); + } + } + + + + + public override IAsyncResult BeginWrite(Byte[] buffer, Int32 offset, Int32 count, AsyncCallback callback, Object state) { + + if (buffer == null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // Previous version incorrectly threw NotSupported instead of ObjectDisposed. We keep that behaviour for back-compat. + // EnsureNotClosed(); + if (_stream == null) __Error.ReadNotSupported(); + EnsureCanWrite(); + + // Try to satisfy the request from the buffer synchronously. But still need a sem-lock in case that another + // Async IO Task accesses the buffer concurrently. If we fail to acquire the lock without waiting, make this + // an Async operation. + SemaphoreSlim sem = base.EnsureAsyncActiveSemaphoreInitialized(); + Task semaphoreLockTask = sem.WaitAsync(); + if (semaphoreLockTask.Status == TaskStatus.RanToCompletion) { + + bool completeSynchronously = true; + try { + if (_writePos == 0) + ClearReadBufferBeforeWrite(); + + // If the write completely fits into the buffer, we can complete synchronously. + Contract.Assert(_writePos < _bufferSize); + completeSynchronously = (count < _bufferSize - _writePos); + + if (completeSynchronously) { + + Exception error; + WriteToBuffer(buffer, ref offset, ref count, out error); + Contract.Assert(count == 0); + + SynchronousAsyncResult asyncResult = (error == null) + ? new SynchronousAsyncResult(state) + : new SynchronousAsyncResult(error, state, isWrite: true); + if (callback != null) + callback(asyncResult); + + return asyncResult; + } + } finally { + if (completeSynchronously) // if this is FALSE, we will be entering WriteToUnderlyingStreamAsync and releasing there. + sem.Release(); + } + } + + // Delegate to the async implementation. + return BeginWriteToUnderlyingStream(buffer, offset, count, callback, state, semaphoreLockTask); + } + + + private IAsyncResult BeginWriteToUnderlyingStream(Byte[] buffer, Int32 offset, Int32 count, AsyncCallback callback, Object state, + Task semaphoreLockTask) { + + Task writeOp = WriteToUnderlyingStreamAsync(buffer, offset, count, CancellationToken.None, semaphoreLockTask, useApmPattern: true); + return TaskToApm.Begin(writeOp, callback, state); + } + + + public override void EndWrite(IAsyncResult asyncResult) { + + if (asyncResult == null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + var sAR = asyncResult as SynchronousAsyncResult; + if (sAR != null) { + SynchronousAsyncResult.EndWrite(asyncResult); + return; + } + + TaskToApm.End(asyncResult); + } + + + public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) { + + if (buffer == null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // Fast path check for cancellation already requested + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<Int32>(cancellationToken); + + EnsureNotClosed(); + EnsureCanWrite(); + + // Try to satisfy the request from the buffer synchronously. But still need a sem-lock in case that another + // Async IO Task accesses the buffer concurrently. If we fail to acquire the lock without waiting, make this + // an Async operation. + SemaphoreSlim sem = base.EnsureAsyncActiveSemaphoreInitialized(); + Task semaphoreLockTask = sem.WaitAsync(); + if (semaphoreLockTask.Status == TaskStatus.RanToCompletion) { + + bool completeSynchronously = true; + try { + + if (_writePos == 0) + ClearReadBufferBeforeWrite(); + + Contract.Assert(_writePos < _bufferSize); + + // If the write completely fits into the buffer, we can complete synchronously: + completeSynchronously = (count < _bufferSize - _writePos); + + if (completeSynchronously) { + + Exception error; + WriteToBuffer(buffer, ref offset, ref count, out error); + Contract.Assert(count == 0); + + return (error == null) + ? Task.CompletedTask + : Task.FromException(error); + } + } finally { + if (completeSynchronously) // if this is FALSE, we will be entering WriteToUnderlyingStreamAsync and releasing there. + sem.Release(); + } + } + + // Delegate to the async implementation. + return WriteToUnderlyingStreamAsync(buffer, offset, count, cancellationToken, semaphoreLockTask, useApmPattern: false); + } + + + /// <summary>BufferedStream should be as thin a wrapper as possible. We want that WriteAsync delegates to + /// WriteAsync of the underlying _stream and that BeginWrite delegates to BeginWrite of the underlying stream, + /// rather than calling the base Stream which implements the one in terms of the other. This allows BufferedStream + /// to affect the semantics of the stream it wraps as little as possible. At the same time, we want to share as + /// much code between the APM and the Async pattern implementations as possible. This method is called by both with + /// a corresponding useApmPattern value. Recall that Task implements IAsyncResult.</summary> + private async Task WriteToUnderlyingStreamAsync(Byte[] array, Int32 offset, Int32 count, + CancellationToken cancellationToken, + Task semaphoreLockTask, bool useApmPattern) { + + // (These should be Contract.Requires(..) but that method had some issues in async methods; using Assert(..) for now.) + Contract.Assert(array != null); + Contract.Assert(offset >= 0); + Contract.Assert(count >= 0); + Contract.Assert(array.Length - offset >= count); + Contract.Assert(_stream != null); + Contract.Assert(_stream.CanWrite); + Contract.Assert(_bufferSize > 0); + Contract.Assert(semaphoreLockTask != null); + + // See the LARGE COMMENT in Write(..) for the explanation of the write buffer algorithm. + + await semaphoreLockTask.ConfigureAwait(false); + try { + + // The buffer might have been changed by another async task while we were waiting on the semaphore. + // However, note that if we recalculate the sync completion condition to TRUE, then useBuffer will also be TRUE. + + if (_writePos == 0) + ClearReadBufferBeforeWrite(); + + Int32 totalUserBytes; + bool useBuffer; + checked { // We do not expect buffer sizes big enough for an overflow, but if it happens, lets fail early: + totalUserBytes = _writePos + count; + useBuffer = (totalUserBytes + count < (_bufferSize + _bufferSize)); + } + + if (useBuffer) { + + WriteToBuffer(array, ref offset, ref count); + + if (_writePos < _bufferSize) { + + Contract.Assert(count == 0); + return; + } + + Contract.Assert(count >= 0); + Contract.Assert(_writePos == _bufferSize); + Contract.Assert(_buffer != null); + + if (useApmPattern) { + EnsureBeginEndAwaitableAllocated(); + _stream.BeginWrite(_buffer, 0, _writePos, BeginEndAwaitableAdapter.Callback, _beginEndAwaitable); + _stream.EndWrite(await _beginEndAwaitable); + } else { + await _stream.WriteAsync(_buffer, 0, _writePos, cancellationToken).ConfigureAwait(false); + } + _writePos = 0; + + WriteToBuffer(array, ref offset, ref count); + + Contract.Assert(count == 0); + Contract.Assert(_writePos < _bufferSize); + + } else { // if (!useBuffer) + + // Write out the buffer if necessary. + if (_writePos > 0) { + + Contract.Assert(_buffer != null); + Contract.Assert(totalUserBytes >= _bufferSize); + + // Try avoiding extra write to underlying stream by combining previously buffered data with current user data: + if (totalUserBytes <= (_bufferSize + _bufferSize) && totalUserBytes <= MaxShadowBufferSize) { + + EnsureShadowBufferAllocated(); + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, count); + if (useApmPattern) { + EnsureBeginEndAwaitableAllocated(); + _stream.BeginWrite(_buffer, 0, totalUserBytes, BeginEndAwaitableAdapter.Callback, _beginEndAwaitable); + _stream.EndWrite(await _beginEndAwaitable); + } else { + await _stream.WriteAsync(_buffer, 0, totalUserBytes, cancellationToken).ConfigureAwait(false); + } + _writePos = 0; + return; + } + + if (useApmPattern) { + EnsureBeginEndAwaitableAllocated(); + _stream.BeginWrite(_buffer, 0, _writePos, BeginEndAwaitableAdapter.Callback, _beginEndAwaitable); + _stream.EndWrite(await _beginEndAwaitable); + } else { + await _stream.WriteAsync(_buffer, 0, _writePos, cancellationToken).ConfigureAwait(false); + } + _writePos = 0; + } + + // Write out user data. + if (useApmPattern) { + EnsureBeginEndAwaitableAllocated(); + _stream.BeginWrite(array, offset, count, BeginEndAwaitableAdapter.Callback, _beginEndAwaitable); + _stream.EndWrite(await _beginEndAwaitable); + } else { + await _stream.WriteAsync(array, offset, count, cancellationToken).ConfigureAwait(false); + } + } + } finally { + SemaphoreSlim sem = base.EnsureAsyncActiveSemaphoreInitialized(); + sem.Release(); + } + } + + + public override void WriteByte(Byte value) { + + EnsureNotClosed(); + + if (_writePos == 0) { + + EnsureCanWrite(); + ClearReadBufferBeforeWrite(); + EnsureBufferAllocated(); + } + + // We should not be flushing here, but only writing to the underlying stream, but previous version flushed, so we keep this. + if (_writePos >= _bufferSize - 1) + FlushWrite(); + + _buffer[_writePos++] = value; + + Contract.Assert(_writePos < _bufferSize); + } + + + public override Int64 Seek(Int64 offset, SeekOrigin origin) { + + EnsureNotClosed(); + EnsureCanSeek(); + + // If we have bytes in the WRITE buffer, flush them out, seek and be done. + if (_writePos > 0) { + + // We should be only writing the buffer and not flushing, + // but the previous version did flush and we stick to it for back-compat reasons. + FlushWrite(); + return _stream.Seek(offset, origin); + } + + // The buffer is either empty or we have a buffered READ. + + if (_readLen - _readPos > 0 && origin == SeekOrigin.Current) { + + // If we have bytes in the READ buffer, adjust the seek offset to account for the resulting difference + // between this stream's position and the underlying stream's position. + offset -= (_readLen - _readPos); + } + + Int64 oldPos = Position; + Contract.Assert(oldPos == _stream.Position + (_readPos - _readLen)); + + Int64 newPos = _stream.Seek(offset, origin); + + // If the seek destination is still within the data currently in the buffer, we want to keep the buffer data and continue using it. + // Otherwise we will throw away the buffer. This can only happen on READ, as we flushed WRITE data above. + + // The offset of the new/updated seek pointer within _buffer: + _readPos = (Int32) (newPos - (oldPos - _readPos)); + + // If the offset of the updated seek pointer in the buffer is still legal, then we can keep using the buffer: + if (0 <= _readPos && _readPos < _readLen) { + + // Adjust the seek pointer of the underlying stream to reflect the amount of useful bytes in the read buffer: + _stream.Seek(_readLen - _readPos, SeekOrigin.Current); + + } else { // The offset of the updated seek pointer is not a legal offset. Loose the buffer. + + _readPos = _readLen = 0; + } + + Contract.Assert(newPos == Position, "newPos (=" + newPos + ") == Position (=" + Position + ")"); + return newPos; + } + + + public override void SetLength(Int64 value) { + + if (value < 0) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NegFileSize")); + Contract.EndContractBlock(); + + EnsureNotClosed(); + EnsureCanSeek(); + EnsureCanWrite(); + + Flush(); + _stream.SetLength(value); + } + +} // class BufferedStream +} // namespace diff --git a/src/mscorlib/src/System/IO/Directory.cs b/src/mscorlib/src/System/IO/Directory.cs new file mode 100644 index 0000000000..be74538d2d --- /dev/null +++ b/src/mscorlib/src/System/IO/Directory.cs @@ -0,0 +1,1389 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Exposes routines for enumerating through a +** directory. +** +** April 11,2000 +** +===========================================================*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Security; +using System.Security.Permissions; +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; +using System.Text; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; +using System.Threading; + +#if FEATURE_MACL +using System.Security.AccessControl; +#endif + +namespace System.IO { + [ComVisible(true)] + public static class Directory { + public static DirectoryInfo GetParent(String path) + { + if (path==null) + throw new ArgumentNullException("path"); + + if (path.Length==0) + throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path"); + Contract.EndContractBlock(); + + String fullPath = Path.GetFullPathInternal(path); + + String s = Path.GetDirectoryName(fullPath); + if (s==null) + return null; + return new DirectoryInfo(s); + } + + [System.Security.SecuritySafeCritical] + public static DirectoryInfo CreateDirectory(String path) { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty")); + Contract.EndContractBlock(); + + return InternalCreateDirectoryHelper(path, true); + } + + [System.Security.SecurityCritical] + internal static DirectoryInfo UnsafeCreateDirectory(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty")); + Contract.EndContractBlock(); + + return InternalCreateDirectoryHelper(path, false); + } + + [System.Security.SecurityCritical] + internal static DirectoryInfo InternalCreateDirectoryHelper(String path, bool checkHost) + { + Contract.Requires(path != null); + Contract.Requires(path.Length != 0); + + String fullPath = Path.GetFullPathInternal(path); + + // You need read access to the directory to be returned back and write access to all the directories + // that you need to create. If we fail any security checks we will not create any directories at all. + // We attempt to create directories only after all the security checks have passed. This is avoid doing + // a demand at every level. + String demandDir = GetDemandDir(fullPath, true); + +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, demandDir); + state.EnsureState(); // do the check on the AppDomainManager to make sure this is allowed + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, demandDir, false, false); +#endif + + InternalCreateDirectory(fullPath, path, null, checkHost); + + return new DirectoryInfo(fullPath, false); + } + +#if FEATURE_MACL + [System.Security.SecuritySafeCritical] // auto-generated + public static DirectoryInfo CreateDirectory(String path, DirectorySecurity directorySecurity) { + if (path==null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty")); + Contract.EndContractBlock(); + + String fullPath = Path.GetFullPathInternal(path); + + // You need read access to the directory to be returned back and write access to all the directories + // that you need to create. If we fail any security checks we will not create any directories at all. + // We attempt to create directories only after all the security checks have passed. This is avoid doing + // a demand at every level. + String demandDir = GetDemandDir(fullPath, true); + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, demandDir, false, false ); + + InternalCreateDirectory(fullPath, path, directorySecurity); + + return new DirectoryInfo(fullPath, false); + } +#endif // FEATURE_MACL + + // Input to this method should already be fullpath. This method will ensure that we append + // the trailing slash only when appropriate and when thisDirOnly is specified append a "." + // at the end of the path to indicate that the demand is only for the fullpath and not + // everything underneath it. + internal static String GetDemandDir(string fullPath, bool thisDirOnly) + { + String demandPath; + + if (thisDirOnly) { + if (fullPath.EndsWith( Path.DirectorySeparatorChar ) + || fullPath.EndsWith( Path.AltDirectorySeparatorChar ) ) + demandPath = fullPath + "."; + else + demandPath = fullPath + Path.DirectorySeparatorCharAsString + "."; + } + else { + if (!(fullPath.EndsWith( Path.DirectorySeparatorChar ) + || fullPath.EndsWith( Path.AltDirectorySeparatorChar )) ) + demandPath = fullPath + Path.DirectorySeparatorCharAsString; + else + demandPath = fullPath; + } + return demandPath; + } + + internal static void InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj) + { + InternalCreateDirectory(fullPath, path, dirSecurityObj, false); + } + + + [System.Security.SecuritySafeCritical] + internal unsafe static void InternalCreateDirectory(String fullPath, String path, Object dirSecurityObj, bool checkHost) + { +#if FEATURE_MACL + DirectorySecurity dirSecurity = (DirectorySecurity)dirSecurityObj; +#endif // FEATURE_MACL + + int length = fullPath.Length; + + // We need to trim the trailing slash or the code will try to create 2 directories of the same name. + if (length >= 2 && Path.IsDirectorySeparator(fullPath[length - 1])) + length--; + + int lengthRoot = Path.GetRootLength(fullPath); + + // For UNC paths that are only // or /// + if (length == 2 && Path.IsDirectorySeparator(fullPath[1])) + throw new IOException(Environment.GetResourceString("IO.IO_CannotCreateDirectory", path)); + + // We can save a bunch of work if the directory we want to create already exists. This also + // saves us in the case where sub paths are inaccessible (due to ERROR_ACCESS_DENIED) but the + // final path is accessable and the directory already exists. For example, consider trying + // to create c:\Foo\Bar\Baz, where everything already exists but ACLS prevent access to c:\Foo + // and c:\Foo\Bar. In that case, this code will think it needs to create c:\Foo, and c:\Foo\Bar + // and fail to due so, causing an exception to be thrown. This is not what we want. + if (InternalExists(fullPath)) { + return; + } + + List<string> stackDir = new List<string>(); + + // Attempt to figure out which directories don't exist, and only + // create the ones we need. Note that InternalExists may fail due + // to Win32 ACL's preventing us from seeing a directory, and this + // isn't threadsafe. + + bool somepathexists = false; + + if (length > lengthRoot) { // Special case root (fullpath = X:\\) + int i = length-1; + while (i >= lengthRoot && !somepathexists) { + String dir = fullPath.Substring(0, i+1); + + if (!InternalExists(dir)) // Create only the ones missing + stackDir.Add(dir); + else + somepathexists = true; + + while (i > lengthRoot && fullPath[i] != Path.DirectorySeparatorChar && fullPath[i] != Path.AltDirectorySeparatorChar) i--; + i--; + } + } + + int count = stackDir.Count; + + if (stackDir.Count != 0 +#if FEATURE_CAS_POLICY + // All demands in full trust domains are no-ops, so skip + // + // The full path went through validity checks by being passed through FileIOPermissions already. + // As a sub string of the full path can't fail the checks if the full path passes. + && !CodeAccessSecurityEngine.QuickCheckForAllDemands() +#endif + ) + { + String[] securityList = new String[stackDir.Count]; + stackDir.CopyTo(securityList, 0); + for (int j = 0 ; j < securityList.Length; j++) + securityList[j] += "\\."; // leaf will never have a slash at the end + + // Security check for all directories not present only. +#if FEATURE_MACL + AccessControlActions control = (dirSecurity == null) ? AccessControlActions.None : AccessControlActions.Change; + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, control, securityList, false, false); +#else +#if FEATURE_CORECLR + if (checkHost) + { + foreach (String demandPath in securityList) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, demandPath); + state.EnsureState(); + } + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, securityList, false, false); +#endif +#endif //FEATURE_MACL + } + + // If we were passed a DirectorySecurity, convert it to a security + // descriptor and set it in he call to CreateDirectory. + Win32Native.SECURITY_ATTRIBUTES secAttrs = null; +#if FEATURE_MACL + if (dirSecurity != null) { + secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); + + // For ACL's, get the security descriptor from the FileSecurity. + byte[] sd = dirSecurity.GetSecurityDescriptorBinaryForm(); + byte * bytesOnStack = stackalloc byte[sd.Length]; + Buffer.Memcpy(bytesOnStack, 0, sd, 0, sd.Length); + secAttrs.pSecurityDescriptor = bytesOnStack; + } +#endif + + bool r = true; + int firstError = 0; + String errorString = path; + // If all the security checks succeeded create all the directories + while (stackDir.Count > 0) { + String name = stackDir[stackDir.Count - 1]; + stackDir.RemoveAt(stackDir.Count - 1); + if (PathInternal.IsDirectoryTooLong(name)) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + r = Win32Native.CreateDirectory(name, secAttrs); + if (!r && (firstError == 0)) { + int currentError = Marshal.GetLastWin32Error(); + // While we tried to avoid creating directories that don't + // exist above, there are at least two cases that will + // cause us to see ERROR_ALREADY_EXISTS here. InternalExists + // can fail because we didn't have permission to the + // directory. Secondly, another thread or process could + // create the directory between the time we check and the + // time we try using the directory. Thirdly, it could + // fail because the target does exist, but is a file. + if (currentError != Win32Native.ERROR_ALREADY_EXISTS) + firstError = currentError; + else { + // If there's a file in this directory's place, or if we have ERROR_ACCESS_DENIED when checking if the directory already exists throw. + if (File.InternalExists(name) || (!InternalExists(name, out currentError) && currentError == Win32Native.ERROR_ACCESS_DENIED)) { + firstError = currentError; + // Give the user a nice error message, but don't leak path information. + try { +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, GetDemandDir(name, true)); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, GetDemandDir(name, true)); +#endif // FEATURE_CORECLR + errorString = name; + } + catch(SecurityException) {} + } + } + } + } + + // We need this check to mask OS differences + // Handle CreateDirectory("X:\\foo") when X: doesn't exist. Similarly for n/w paths. + if ((count == 0) && !somepathexists) { + String root = InternalGetDirectoryRoot(fullPath); + if (!InternalExists(root)) { + // Extract the root from the passed in path again for security. + __Error.WinIOError(Win32Native.ERROR_PATH_NOT_FOUND, InternalGetDirectoryRoot(path)); + } + return; + } + + // Only throw an exception if creating the exact directory we + // wanted failed to work correctly. + if (!r && (firstError != 0)) { + __Error.WinIOError(firstError, errorString); + } + } + + + // Tests if the given path refers to an existing DirectoryInfo on disk. + // + // Your application must have Read permission to the directory's + // contents. + // + [System.Security.SecuritySafeCritical] // auto-generated + public static bool Exists(String path) + { + return InternalExistsHelper(path, true); + } + + [System.Security.SecurityCritical] + internal static bool UnsafeExists(String path) + { + return InternalExistsHelper(path, false); + } + + [System.Security.SecurityCritical] + internal static bool InternalExistsHelper(String path, bool checkHost) { + try + { + if (path == null) + return false; + if (path.Length == 0) + return false; + + // Get fully qualified file name ending in \* for security check + + String fullPath = Path.GetFullPathInternal(path); + String demandPath = GetDemandDir(fullPath, true); + +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, demandPath); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, demandPath, false, false); +#endif + + + return InternalExists(fullPath); + } + catch (ArgumentException) { } + catch (NotSupportedException) { } // Security can throw this on ":" + catch (SecurityException) { } + catch (IOException) { } + catch (UnauthorizedAccessException) + { + Contract.Assert(false, "Ignore this assert and send a repro to Microsoft. This assert was tracking purposes only."); + } + return false; + } + + // Determine whether path describes an existing directory + // on disk, avoiding security checks. + [System.Security.SecurityCritical] // auto-generated + internal static bool InternalExists(String path) { + int lastError = Win32Native.ERROR_SUCCESS; + return InternalExists(path, out lastError); + } + + // Determine whether path describes an existing directory + // on disk, avoiding security checks. + [System.Security.SecurityCritical] // auto-generated + internal static bool InternalExists(String path, out int lastError) { + Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + lastError = File.FillAttributeInfo(path, ref data, false, true); + + return (lastError == 0) && (data.fileAttributes != -1) + && ((data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) != 0); + } + + public static void SetCreationTime(String path,DateTime creationTime) + { + SetCreationTimeUtc(path, creationTime.ToUniversalTime()); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe static void SetCreationTimeUtc(String path,DateTime creationTimeUtc) + { + using (SafeFileHandle handle = Directory.OpenHandle(path)) { + Win32Native.FILE_TIME fileTime = new Win32Native.FILE_TIME(creationTimeUtc.ToFileTimeUtc()); + bool r = Win32Native.SetFileTime(handle, &fileTime, null, null); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + __Error.WinIOError(errorCode, path); + } + } + } + + public static DateTime GetCreationTime(String path) + { + return File.GetCreationTime(path); + } + + public static DateTime GetCreationTimeUtc(String path) + { + return File.GetCreationTimeUtc(path); + } + + public static void SetLastWriteTime(String path,DateTime lastWriteTime) + { + SetLastWriteTimeUtc(path, lastWriteTime.ToUniversalTime()); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe static void SetLastWriteTimeUtc(String path,DateTime lastWriteTimeUtc) + { + using (SafeFileHandle handle = Directory.OpenHandle(path)) { + Win32Native.FILE_TIME fileTime = new Win32Native.FILE_TIME(lastWriteTimeUtc.ToFileTimeUtc()); + bool r = Win32Native.SetFileTime(handle, null, null, &fileTime); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + __Error.WinIOError(errorCode, path); + } + } + } + + public static DateTime GetLastWriteTime(String path) + { + return File.GetLastWriteTime(path); + } + + public static DateTime GetLastWriteTimeUtc(String path) + { + return File.GetLastWriteTimeUtc(path); + } + + public static void SetLastAccessTime(String path,DateTime lastAccessTime) + { + SetLastAccessTimeUtc(path, lastAccessTime.ToUniversalTime()); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe static void SetLastAccessTimeUtc(String path,DateTime lastAccessTimeUtc) + { + using (SafeFileHandle handle = Directory.OpenHandle(path)) { + Win32Native.FILE_TIME fileTime = new Win32Native.FILE_TIME(lastAccessTimeUtc.ToFileTimeUtc()); + bool r = Win32Native.SetFileTime(handle, null, &fileTime, null); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + __Error.WinIOError(errorCode, path); + } + } + } + + public static DateTime GetLastAccessTime(String path) + { + return File.GetLastAccessTime(path); + } + + public static DateTime GetLastAccessTimeUtc(String path) + { + return File.GetLastAccessTimeUtc(path); + } + +#if FEATURE_MACL + public static DirectorySecurity GetAccessControl(String path) + { + return new DirectorySecurity(path, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + public static DirectorySecurity GetAccessControl(String path, AccessControlSections includeSections) + { + return new DirectorySecurity(path, includeSections); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void SetAccessControl(String path, DirectorySecurity directorySecurity) + { + if (directorySecurity == null) + throw new ArgumentNullException("directorySecurity"); + Contract.EndContractBlock(); + + String fullPath = Path.GetFullPathInternal(path); + directorySecurity.Persist(fullPath); + } +#endif + + // Returns an array of filenames in the DirectoryInfo specified by path + public static String[] GetFiles(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetFiles(path, "*", SearchOption.TopDirectoryOnly); + } + + // Returns an array of Files in the current DirectoryInfo matching the + // given search pattern (ie, "*.txt"). + public static String[] GetFiles(String path, String searchPattern) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetFiles(path, searchPattern, SearchOption.TopDirectoryOnly); + } + + // Returns an array of Files in the current DirectoryInfo matching the + // given search pattern (ie, "*.txt") and search option + public static String[] GetFiles(String path, String searchPattern, SearchOption searchOption) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetFiles(path, searchPattern, searchOption); + } + + // Returns an array of Files in the current DirectoryInfo matching the + // given search pattern (ie, "*.txt") and search option + private static String[] InternalGetFiles(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + return InternalGetFileDirectoryNames(path, path, searchPattern, true, false, searchOption, true); + } + + [System.Security.SecurityCritical] + internal static String[] UnsafeGetFiles(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + return InternalGetFileDirectoryNames(path, path, searchPattern, true, false, searchOption, false); + } + + // Returns an array of Directories in the current directory. + public static String[] GetDirectories(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetDirectories(path, "*", SearchOption.TopDirectoryOnly); + } + + // Returns an array of Directories in the current DirectoryInfo matching the + // given search criteria (ie, "*.txt"). + public static String[] GetDirectories(String path, String searchPattern) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetDirectories(path, searchPattern, SearchOption.TopDirectoryOnly); + } + + // Returns an array of Directories in the current DirectoryInfo matching the + // given search criteria (ie, "*.txt"). + public static String[] GetDirectories(String path, String searchPattern, SearchOption searchOption) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetDirectories(path, searchPattern, searchOption); + } + + // Returns an array of Directories in the current DirectoryInfo matching the + // given search criteria (ie, "*.txt"). + private static String[] InternalGetDirectories(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + Contract.Ensures(Contract.Result<String[]>() != null); + + return InternalGetFileDirectoryNames(path, path, searchPattern, false, true, searchOption, true); + } + + [System.Security.SecurityCritical] + internal static String[] UnsafeGetDirectories(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + Contract.Ensures(Contract.Result<String[]>() != null); + + return InternalGetFileDirectoryNames(path, path, searchPattern, false, true, searchOption, false); + } + + // Returns an array of strongly typed FileSystemInfo entries in the path + public static String[] GetFileSystemEntries(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly); + } + + // Returns an array of strongly typed FileSystemInfo entries in the path with the + // given search criteria (ie, "*.txt"). We disallow .. as a part of the search criteria + public static String[] GetFileSystemEntries(String path, String searchPattern) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetFileSystemEntries(path, searchPattern, SearchOption.TopDirectoryOnly); + } + + // Returns an array of strongly typed FileSystemInfo entries in the path with the + // given search criteria (ie, "*.txt"). We disallow .. as a part of the search criteria + public static String[] GetFileSystemEntries(String path, String searchPattern, SearchOption searchOption) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.Ensures(Contract.Result<String[]>() != null); + Contract.EndContractBlock(); + + return InternalGetFileSystemEntries(path, searchPattern, searchOption); + } + + private static String[] InternalGetFileSystemEntries(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + return InternalGetFileDirectoryNames(path, path, searchPattern, true, true, searchOption, true); + } + + + // Private class that holds search data that is passed around + // in the heap based stack recursion + internal sealed class SearchData + { + public SearchData(String fullPath, String userPath, SearchOption searchOption) + { + Contract.Requires(fullPath != null && fullPath.Length > 0); + Contract.Requires(userPath != null && userPath.Length > 0); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + this.fullPath = fullPath; + this.userPath = userPath; + this.searchOption = searchOption; + } + + public readonly string fullPath; // Fully qualified search path excluding the search criteria in the end (ex, c:\temp\bar\foo) + public readonly string userPath; // User specified path (ex, bar\foo) + public readonly SearchOption searchOption; + } + + + // Returns fully qualified user path of dirs/files that matches the search parameters. + // For recursive search this method will search through all the sub dirs and execute + // the given search criteria against every dir. + // For all the dirs/files returned, it will then demand path discovery permission for + // their parent folders (it will avoid duplicate permission checks) + internal static String[] InternalGetFileDirectoryNames(String path, String userPathOriginal, String searchPattern, bool includeFiles, bool includeDirs, SearchOption searchOption, bool checkHost) + { + Contract.Requires(path != null); + Contract.Requires(userPathOriginal != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + IEnumerable<String> enble = FileSystemEnumerableFactory.CreateFileNameIterator( + path, userPathOriginal, searchPattern, + includeFiles, includeDirs, searchOption, checkHost); + List<String> fileList = new List<String>(enble); + return fileList.ToArray(); + } + + public static IEnumerable<String> EnumerateDirectories(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + return InternalEnumerateDirectories(path, "*", SearchOption.TopDirectoryOnly); + } + + public static IEnumerable<String> EnumerateDirectories(String path, String searchPattern) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.EndContractBlock(); + + return InternalEnumerateDirectories(path, searchPattern, SearchOption.TopDirectoryOnly); + } + + public static IEnumerable<String> EnumerateDirectories(String path, String searchPattern, SearchOption searchOption) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.EndContractBlock(); + + return InternalEnumerateDirectories(path, searchPattern, searchOption); + } + + private static IEnumerable<String> InternalEnumerateDirectories(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + return EnumerateFileSystemNames(path, searchPattern, searchOption, false, true); + } + + public static IEnumerable<String> EnumerateFiles(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + Contract.EndContractBlock(); + + return InternalEnumerateFiles(path, "*", SearchOption.TopDirectoryOnly); + } + + public static IEnumerable<String> EnumerateFiles(String path, String searchPattern) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + Contract.EndContractBlock(); + + return InternalEnumerateFiles(path, searchPattern, SearchOption.TopDirectoryOnly); + } + + public static IEnumerable<String> EnumerateFiles(String path, String searchPattern, SearchOption searchOption) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + Contract.EndContractBlock(); + + return InternalEnumerateFiles(path, searchPattern, searchOption); + } + + private static IEnumerable<String> InternalEnumerateFiles(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + + return EnumerateFileSystemNames(path, searchPattern, searchOption, true, false); + } + + public static IEnumerable<String> EnumerateFileSystemEntries(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + Contract.EndContractBlock(); + + return InternalEnumerateFileSystemEntries(path, "*", SearchOption.TopDirectoryOnly); + } + + public static IEnumerable<String> EnumerateFileSystemEntries(String path, String searchPattern) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + Contract.EndContractBlock(); + + return InternalEnumerateFileSystemEntries(path, searchPattern, SearchOption.TopDirectoryOnly); + } + + public static IEnumerable<String> EnumerateFileSystemEntries(String path, String searchPattern, SearchOption searchOption) + { + if (path == null) + throw new ArgumentNullException("path"); + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + Contract.EndContractBlock(); + + return InternalEnumerateFileSystemEntries(path, searchPattern, searchOption); + } + + private static IEnumerable<String> InternalEnumerateFileSystemEntries(String path, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + + return EnumerateFileSystemNames(path, searchPattern, searchOption, true, true); + } + + private static IEnumerable<String> EnumerateFileSystemNames(String path, String searchPattern, SearchOption searchOption, + bool includeFiles, bool includeDirs) + { + Contract.Requires(path != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + Contract.Ensures(Contract.Result<IEnumerable<String>>() != null); + + return FileSystemEnumerableFactory.CreateFileNameIterator(path, path, searchPattern, + includeFiles, includeDirs, searchOption, true); + } + + // Retrieves the names of the logical drives on this machine in the + // form "C:\". + // + // Your application must have System Info permission. + // + [System.Security.SecuritySafeCritical] // auto-generated + public static String[] GetLogicalDrives() + { + Contract.Ensures(Contract.Result<String[]>() != null); + +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); +#pragma warning restore 618 + + int drives = Win32Native.GetLogicalDrives(); + if (drives==0) + __Error.WinIOError(); + uint d = (uint)drives; + int count = 0; + while (d != 0) { + if (((int)d & 1) != 0) count++; + d >>= 1; + } + String[] result = new String[count]; + char[] root = new char[] {'A', ':', '\\'}; + d = (uint)drives; + count = 0; + while (d != 0) { + if (((int)d & 1) != 0) { + result[count++] = new String(root); + } + d >>= 1; + root[0]++; + } + return result; + } + + [System.Security.SecuritySafeCritical] + public static String GetDirectoryRoot(String path) { + if (path==null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + String fullPath = Path.GetFullPathInternal(path); + String root = fullPath.Substring(0, Path.GetRootLength(fullPath)); + String demandPath = GetDemandDir(root, true); + +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, demandPath); + state.EnsureState(); +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandPath, false, false); +#endif + + return root; + } + + internal static String InternalGetDirectoryRoot(String path) { + if (path == null) return null; + return path.Substring(0, Path.GetRootLength(path)); + } + + /*===============================CurrentDirectory=============================== + **Action: Provides a getter and setter for the current directory. The original + ** current DirectoryInfo is the one from which the process was started. + **Returns: The current DirectoryInfo (from the getter). Void from the setter. + **Arguments: The current DirectoryInfo to which to switch to the setter. + **Exceptions: + ==============================================================================*/ + [System.Security.SecuritySafeCritical] + public static String GetCurrentDirectory() + { + return InternalGetCurrentDirectory(true); + } + + [System.Security.SecurityCritical] + internal static String UnsafeGetCurrentDirectory() + { + return InternalGetCurrentDirectory(false); + } + + [System.Security.SecuritySafeCritical] + private static string InternalGetCurrentDirectory(bool checkHost) + { + string currentDirectory = ( +#if FEATURE_PATHCOMPAT + AppContextSwitches.UseLegacyPathHandling ? LegacyGetCurrentDirectory() : +#endif + NewGetCurrentDirectory()); + + string demandPath = GetDemandDir(currentDirectory, true); + +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPath); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, demandPath, false, false); +#endif + return currentDirectory; + } + +#if FEATURE_PATHCOMPAT + [System.Security.SecurityCritical] + private static String LegacyGetCurrentDirectory() + { + StringBuilder sb = StringBuilderCache.Acquire(Path.MaxPath + 1); + if (Win32Native.GetCurrentDirectory(sb.Capacity, sb) == 0) + __Error.WinIOError(); + String currentDirectory = sb.ToString(); + // Note that if we have somehow put our command prompt into short + // file name mode (ie, by running edlin or a DOS grep, etc), then + // this will return a short file name. + if (currentDirectory.IndexOf('~') >= 0) { + int r = Win32Native.GetLongPathName(currentDirectory, sb, sb.Capacity); + if (r == 0 || r >= Path.MaxPath) { + int errorCode = Marshal.GetLastWin32Error(); + if (r >= Path.MaxPath) + errorCode = Win32Native.ERROR_FILENAME_EXCED_RANGE; + if (errorCode != Win32Native.ERROR_FILE_NOT_FOUND && + errorCode != Win32Native.ERROR_PATH_NOT_FOUND && + errorCode != Win32Native.ERROR_INVALID_FUNCTION && // by design - enough said. + errorCode != Win32Native.ERROR_ACCESS_DENIED) + __Error.WinIOError(errorCode, String.Empty); + } + currentDirectory = sb.ToString(); + } + StringBuilderCache.Release(sb); + String demandPath = GetDemandDir(currentDirectory, true); + + return currentDirectory; + } +#endif // FEATURE_PATHCOMPAT + + [System.Security.SecurityCritical] + private static string NewGetCurrentDirectory() + { + using (StringBuffer buffer = new StringBuffer(PathInternal.MaxShortPath)) + { + uint result = 0; + while ((result = Win32Native.GetCurrentDirectoryW(buffer.CharCapacity, buffer.GetHandle())) > buffer.CharCapacity) + { + // Reported size is greater than the buffer size. Increase the capacity. + // The size returned includes the null only if more space is needed (this case). + buffer.EnsureCharCapacity(result); + } + + if (result == 0) + __Error.WinIOError(); + + buffer.Length = result; + +#if !PLATFORM_UNIX + if (buffer.Contains('~')) + return LongPathHelper.GetLongPathName(buffer); +#endif + + return buffer.ToString(); + } + } + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif + public static void SetCurrentDirectory(String path) + { + if (path==null) + throw new ArgumentNullException("value"); + if (path.Length==0) + throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty")); + Contract.EndContractBlock(); + if (path.Length >= Path.MaxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + // This will have some large effects on the rest of the runtime + // and other appdomains in this process. Demand unmanaged code. +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); +#pragma warning restore 618 + + String fulldestDirName = Path.GetFullPathInternal(path); + + if (!Win32Native.SetCurrentDirectory(fulldestDirName)) { + // If path doesn't exist, this sets last error to 2 (File + // not Found). LEGACY: This may potentially have worked correctly + // on Win9x, maybe. + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == Win32Native.ERROR_FILE_NOT_FOUND) + errorCode = Win32Native.ERROR_PATH_NOT_FOUND; + __Error.WinIOError(errorCode, fulldestDirName); + } + } + + [System.Security.SecuritySafeCritical] + public static void Move(String sourceDirName,String destDirName) { + InternalMove(sourceDirName, destDirName, true); + } + + [System.Security.SecurityCritical] + internal static void UnsafeMove(String sourceDirName,String destDirName) { + InternalMove(sourceDirName, destDirName, false); + } + + [System.Security.SecurityCritical] + private static void InternalMove(String sourceDirName,String destDirName,bool checkHost) { + if (sourceDirName==null) + throw new ArgumentNullException("sourceDirName"); + if (sourceDirName.Length==0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "sourceDirName"); + + if (destDirName==null) + throw new ArgumentNullException("destDirName"); + if (destDirName.Length==0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destDirName"); + Contract.EndContractBlock(); + + String fullsourceDirName = Path.GetFullPathInternal(sourceDirName); + String sourcePath = GetDemandDir(fullsourceDirName, false); + + if (PathInternal.IsDirectoryTooLong(sourcePath)) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + String fulldestDirName = Path.GetFullPathInternal(destDirName); + String destPath = GetDemandDir(fulldestDirName, false); + + if (PathInternal.IsDirectoryTooLong(destPath)) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + +#if FEATURE_CORECLR + if (checkHost) { + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Write | FileSecurityStateAccess.Read, sourceDirName, sourcePath); + FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Write, destDirName, destPath); + sourceState.EnsureState(); + destState.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, sourcePath, false, false); + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, destPath, false, false); +#endif + + if (String.Compare(sourcePath, destPath, StringComparison.OrdinalIgnoreCase) == 0) + throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustBeDifferent")); + + String sourceRoot = Path.GetPathRoot(sourcePath); + String destinationRoot = Path.GetPathRoot(destPath); + if (String.Compare(sourceRoot, destinationRoot, StringComparison.OrdinalIgnoreCase) != 0) + throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustHaveSameRoot")); + + if (!Win32Native.MoveFile(sourceDirName, destDirName)) + { + int hr = Marshal.GetLastWin32Error(); + if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // Source dir not found + { + hr = Win32Native.ERROR_PATH_NOT_FOUND; + __Error.WinIOError(hr, fullsourceDirName); + } + // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. + if (hr == Win32Native.ERROR_ACCESS_DENIED) // WinNT throws IOException. This check is for Win9x. We can't change it for backcomp. + throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", sourceDirName), Win32Native.MakeHRFromErrorCode(hr)); + __Error.WinIOError(hr, String.Empty); + } + } + + [System.Security.SecuritySafeCritical] + public static void Delete(String path) + { + String fullPath = Path.GetFullPathInternal(path); + Delete(fullPath, path, false, true); + } + + [System.Security.SecuritySafeCritical] + public static void Delete(String path, bool recursive) + { + String fullPath = Path.GetFullPathInternal(path); + Delete(fullPath, path, recursive, true); + } + + [System.Security.SecurityCritical] + internal static void UnsafeDelete(String path, bool recursive) + { + String fullPath = Path.GetFullPathInternal(path); + Delete(fullPath, path, recursive, false); + } + + // Called from DirectoryInfo as well. FullPath is fully qualified, + // while the user path is used for feedback in exceptions. + [System.Security.SecurityCritical] // auto-generated + internal static void Delete(String fullPath, String userPath, bool recursive, bool checkHost) + { + String demandPath; + + // If not recursive, do permission check only on this directory + // else check for the whole directory structure rooted below + demandPath = GetDemandDir(fullPath, !recursive); + +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, userPath, demandPath); + state.EnsureState(); + } +#else + // Make sure we have write permission to this directory + new FileIOPermission(FileIOPermissionAccess.Write, new String[] { demandPath }, false, false ).Demand(); +#endif + + // Do not recursively delete through reparse points. Perhaps in a + // future version we will add a new flag to control this behavior, + // but for now we're much safer if we err on the conservative side. + // This applies to symbolic links and mount points. + Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + int dataInitialised = File.FillAttributeInfo(fullPath, ref data, false, true); + if (dataInitialised != 0) { + // Ensure we throw a DirectoryNotFoundException. + if (dataInitialised == Win32Native.ERROR_FILE_NOT_FOUND) + dataInitialised = Win32Native.ERROR_PATH_NOT_FOUND; + __Error.WinIOError(dataInitialised, fullPath); + } + + if (((FileAttributes)data.fileAttributes & FileAttributes.ReparsePoint) != 0) + recursive = false; + + DeleteHelper(fullPath, userPath, recursive, true); + } + + // Note that fullPath is fully qualified, while userPath may be + // relative. Use userPath for all exception messages to avoid leaking + // fully qualified path information. + [System.Security.SecurityCritical] // auto-generated + private static void DeleteHelper(String fullPath, String userPath, bool recursive, bool throwOnTopLevelDirectoryNotFound) + { + bool r; + int hr; + Exception ex = null; + + // Do not recursively delete through reparse points. Perhaps in a + // future version we will add a new flag to control this behavior, + // but for now we're much safer if we err on the conservative side. + // This applies to symbolic links and mount points. + // Note the logic to check whether fullPath is a reparse point is + // in Delete(String, String, bool), and will set "recursive" to false. + // Note that Win32's DeleteFile and RemoveDirectory will just delete + // the reparse point itself. + + if (recursive) { + Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); + + // Open a Find handle + using (SafeFindHandle hnd = Win32Native.FindFirstFile(fullPath+Path.DirectorySeparatorCharAsString+"*", data)) { + if (hnd.IsInvalid) { + hr = Marshal.GetLastWin32Error(); + __Error.WinIOError(hr, fullPath); + } + + do { + bool isDir = (0!=(data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY)); + if (isDir) { + // Skip ".", "..". + if (data.cFileName.Equals(".") || data.cFileName.Equals("..")) + continue; + + // Recurse for all directories, unless they are + // reparse points. Do not follow mount points nor + // symbolic links, but do delete the reparse point + // itself. + bool shouldRecurse = (0 == (data.dwFileAttributes & (int) FileAttributes.ReparsePoint)); + if (shouldRecurse) { + String newFullPath = Path.InternalCombine(fullPath, data.cFileName); + String newUserPath = Path.InternalCombine(userPath, data.cFileName); + try { + DeleteHelper(newFullPath, newUserPath, recursive, false); + } + catch(Exception e) { + if (ex == null) { + ex = e; + } + } + } + else { + // Check to see if this is a mount point, and + // unmount it. + if (data.dwReserved0 == Win32Native.IO_REPARSE_TAG_MOUNT_POINT) { + // Use full path plus a trailing '\' + String mountPoint = Path.InternalCombine(fullPath, data.cFileName + Path.DirectorySeparatorChar); + r = Win32Native.DeleteVolumeMountPoint(mountPoint); + if (!r) { + hr = Marshal.GetLastWin32Error(); + if (hr != Win32Native.ERROR_PATH_NOT_FOUND) { + try { + __Error.WinIOError(hr, data.cFileName); + } + catch(Exception e) { + if (ex == null) { + ex = e; + } + } + } + } + } + + // RemoveDirectory on a symbolic link will + // remove the link itself. + String reparsePoint = Path.InternalCombine(fullPath, data.cFileName); + r = Win32Native.RemoveDirectory(reparsePoint); + if (!r) { + hr = Marshal.GetLastWin32Error(); + if (hr != Win32Native.ERROR_PATH_NOT_FOUND) { + try { + __Error.WinIOError(hr, data.cFileName); + } + catch(Exception e) { + if (ex == null) { + ex = e; + } + } + } + } + } + } + else { + String fileName = Path.InternalCombine(fullPath, data.cFileName); + r = Win32Native.DeleteFile(fileName); + if (!r) { + hr = Marshal.GetLastWin32Error(); + if (hr != Win32Native.ERROR_FILE_NOT_FOUND) { + try { + __Error.WinIOError(hr, data.cFileName); + } + catch (Exception e) { + if (ex == null) { + ex = e; + } + } + } + } + } + } while (Win32Native.FindNextFile(hnd, data)); + // Make sure we quit with a sensible error. + hr = Marshal.GetLastWin32Error(); + } + + if (ex != null) + throw ex; + if (hr!=0 && hr!=Win32Native.ERROR_NO_MORE_FILES) + __Error.WinIOError(hr, userPath); + } + + r = Win32Native.RemoveDirectory(fullPath); + + if (!r) { + hr = Marshal.GetLastWin32Error(); + if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // A dubious error code. + hr = Win32Native.ERROR_PATH_NOT_FOUND; + // This check was originally put in for Win9x (unfortunately without special casing it to be for Win9x only). We can't change the NT codepath now for backcomp reasons. + if (hr == Win32Native.ERROR_ACCESS_DENIED) + throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", userPath)); + + // don't throw the DirectoryNotFoundException since this is a subdir and there could be a race condition + // between two Directory.Delete callers + if (hr == Win32Native.ERROR_PATH_NOT_FOUND && !throwOnTopLevelDirectoryNotFound) + return; + + __Error.WinIOError(hr, fullPath); + } + } + + // WinNT only. Win9x this code will not work. + [System.Security.SecurityCritical] // auto-generated + private static SafeFileHandle OpenHandle(String path) + { + String fullPath = Path.GetFullPathInternal(path); + String root = Path.GetPathRoot(fullPath); + if (root == fullPath && root[1] == Path.VolumeSeparatorChar) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIsVolume")); + +#if !FEATURE_CORECLR + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, GetDemandDir(fullPath, true), false, false); +#endif + + SafeFileHandle handle = Win32Native.SafeCreateFile ( + fullPath, + GENERIC_WRITE, + (FileShare) (FILE_SHARE_WRITE|FILE_SHARE_DELETE), + null, + FileMode.Open, + FILE_FLAG_BACKUP_SEMANTICS, + IntPtr.Zero + ); + + if (handle.IsInvalid) { + int hr = Marshal.GetLastWin32Error(); + __Error.WinIOError(hr, fullPath); + } + return handle; + } + + private const int FILE_ATTRIBUTE_DIRECTORY = 0x00000010; + private const int GENERIC_WRITE = unchecked((int)0x40000000); + private const int FILE_SHARE_WRITE = 0x00000002; + private const int FILE_SHARE_DELETE = 0x00000004; + private const int OPEN_EXISTING = 0x00000003; + private const int FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; + } + +} + diff --git a/src/mscorlib/src/System/IO/DirectoryInfo.cs b/src/mscorlib/src/System/IO/DirectoryInfo.cs new file mode 100644 index 0000000000..f7b0709e9e --- /dev/null +++ b/src/mscorlib/src/System/IO/DirectoryInfo.cs @@ -0,0 +1,672 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Exposes routines for enumerating through a +** directory. +** +** April 11,2000 +** +===========================================================*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Security; +#if FEATURE_MACL +using System.Security.AccessControl; +#endif +using System.Security.Permissions; +using Microsoft.Win32; +using System.Text; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Runtime.Serialization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; + +namespace System.IO { + [Serializable] + [ComVisible(true)] + public sealed class DirectoryInfo : FileSystemInfo { + private String[] demandDir; + +#if FEATURE_CORECLR + // Migrating InheritanceDemands requires this default ctor, so we can annotate it. +#if FEATURE_CORESYSTEM + [System.Security.SecurityCritical] +#else + [System.Security.SecuritySafeCritical] +#endif //FEATURE_CORESYSTEM + private DirectoryInfo(){} + + + [System.Security.SecurityCritical] + public static DirectoryInfo UnsafeCreateDirectoryInfo(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + DirectoryInfo di = new DirectoryInfo(); + di.Init(path, false); + return di; + } +#endif + + [System.Security.SecuritySafeCritical] + public DirectoryInfo(String path) + { + if (path==null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + Init(path, true); + } + + [System.Security.SecurityCritical] + private void Init(String path, bool checkHost) + { + // Special case "<DriveLetter>:" to point to "<CurrentDirectory>" instead + if ((path.Length == 2) && (path[1] == ':')) + { + OriginalPath = "."; + } + else + { + OriginalPath = path; + } + + // Must fully qualify the path for the security check + String fullPath = Path.GetFullPathInternal(path); + + demandDir = new String[] {Directory.GetDemandDir(fullPath, true)}; +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, OriginalPath, fullPath); + state.EnsureState(); + } +#else + new FileIOPermission(FileIOPermissionAccess.Read, demandDir, false, false ).Demand(); +#endif + + FullPath = fullPath; + DisplayPath = GetDisplayName(OriginalPath, FullPath); + } + +#if FEATURE_CORESYSTEM + [System.Security.SecuritySafeCritical] +#endif //FEATURE_CORESYSTEM + internal DirectoryInfo(String fullPath, bool junk) + { + Contract.Assert(Path.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!"); + // Fast path when we know a DirectoryInfo exists. + OriginalPath = Path.GetFileName(fullPath); + + FullPath = fullPath; + DisplayPath = GetDisplayName(OriginalPath, FullPath); + demandDir = new String[] {Directory.GetDemandDir(fullPath, true)}; + } + + [System.Security.SecurityCritical] // auto-generated + private DirectoryInfo(SerializationInfo info, StreamingContext context) : base(info, context) + { +#if !FEATURE_CORECLR + demandDir = new String[] {Directory.GetDemandDir(FullPath, true)}; + new FileIOPermission(FileIOPermissionAccess.Read, demandDir, false, false ).Demand(); +#endif + DisplayPath = GetDisplayName(OriginalPath, FullPath); + } + + public override String Name { + get + { +#if FEATURE_CORECLR + // DisplayPath is dir name for coreclr + return DisplayPath; +#else + // Return just dir name + return GetDirName(FullPath); +#endif + } + } + + public DirectoryInfo Parent { + [System.Security.SecuritySafeCritical] + get { + String parentName; + // FullPath might be either "c:\bar" or "c:\bar\". Handle + // those cases, as well as avoiding mangling "c:\". + String s = FullPath; + if (s.Length > 3 && s.EndsWith(Path.DirectorySeparatorChar)) + s = FullPath.Substring(0, FullPath.Length - 1); + parentName = Path.GetDirectoryName(s); + if (parentName==null) + return null; + DirectoryInfo dir = new DirectoryInfo(parentName,false); +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery | FileSecurityStateAccess.Read, String.Empty, dir.demandDir[0]); + state.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery | FileIOPermissionAccess.Read, dir.demandDir, false, false).Demand(); +#endif + return dir; + } + } + + +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] +#endif + public DirectoryInfo CreateSubdirectory(String path) { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + return CreateSubdirectory(path, null); + } + +#if FEATURE_MACL + [System.Security.SecuritySafeCritical] // auto-generated + public DirectoryInfo CreateSubdirectory(String path, DirectorySecurity directorySecurity) + { + return CreateSubdirectoryHelper(path, directorySecurity); + } +#else // FEATURE_MACL + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #endif + public DirectoryInfo CreateSubdirectory(String path, Object directorySecurity) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + return CreateSubdirectoryHelper(path, directorySecurity); + } +#endif // FEATURE_MACL + + [System.Security.SecurityCritical] // auto-generated + private DirectoryInfo CreateSubdirectoryHelper(String path, Object directorySecurity) + { + Contract.Requires(path != null); + + String newDirs = Path.InternalCombine(FullPath, path); + String fullPath = Path.GetFullPathInternal(newDirs); + + if (0!=String.Compare(FullPath,0,fullPath,0, FullPath.Length,StringComparison.OrdinalIgnoreCase)) { + String displayPath = __Error.GetDisplayablePath(DisplayPath, false); + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSubPath", path, displayPath)); + } + + // Ensure we have permission to create this subdirectory. + String demandDirForCreation = Directory.GetDemandDir(fullPath, true); +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, OriginalPath, demandDirForCreation); + state.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.Write, new String[] { demandDirForCreation }, false, false).Demand(); +#endif + + Directory.InternalCreateDirectory(fullPath, path, directorySecurity); + + // Check for read permission to directory we hand back by calling this constructor. + return new DirectoryInfo(fullPath); + } + + public void Create() + { + Directory.InternalCreateDirectory(FullPath, OriginalPath, null, true); + } + +#if FEATURE_MACL + public void Create(DirectorySecurity directorySecurity) + { + Directory.InternalCreateDirectory(FullPath, OriginalPath, directorySecurity, true); + } +#endif + + // Tests if the given path refers to an existing DirectoryInfo on disk. + // + // Your application must have Read permission to the directory's + // contents. + // + public override bool Exists { + [System.Security.SecuritySafeCritical] // auto-generated + get + { + try + { + if (_dataInitialised == -1) + Refresh(); + if (_dataInitialised != 0) // Refresh was unable to initialise the data + return false; + + return _data.fileAttributes != -1 && (_data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) != 0; + } + catch + { + return false; + } + } + } + +#if FEATURE_MACL + public DirectorySecurity GetAccessControl() + { + return Directory.GetAccessControl(FullPath, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + public DirectorySecurity GetAccessControl(AccessControlSections includeSections) + { + return Directory.GetAccessControl(FullPath, includeSections); + } + + public void SetAccessControl(DirectorySecurity directorySecurity) + { + Directory.SetAccessControl(FullPath, directorySecurity); + } +#endif + + // Returns an array of Files in the current DirectoryInfo matching the + // given search criteria (ie, "*.txt"). + public FileInfo[] GetFiles(String searchPattern) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.EndContractBlock(); + + return InternalGetFiles(searchPattern, SearchOption.TopDirectoryOnly); + } + + // Returns an array of Files in the current DirectoryInfo matching the + // given search criteria (ie, "*.txt"). + public FileInfo[] GetFiles(String searchPattern, SearchOption searchOption) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.EndContractBlock(); + + return InternalGetFiles(searchPattern, searchOption); + } + + // Returns an array of Files in the current DirectoryInfo matching the + // given search criteria (ie, "*.txt"). + private FileInfo[] InternalGetFiles(String searchPattern, SearchOption searchOption) + { + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + IEnumerable<FileInfo> enble = FileSystemEnumerableFactory.CreateFileInfoIterator(FullPath, OriginalPath, searchPattern, searchOption); + List<FileInfo> fileList = new List<FileInfo>(enble); + return fileList.ToArray(); + } + + // Returns an array of Files in the DirectoryInfo specified by path + public FileInfo[] GetFiles() + { + return InternalGetFiles("*", SearchOption.TopDirectoryOnly); + } + + // Returns an array of Directories in the current directory. + public DirectoryInfo[] GetDirectories() + { + return InternalGetDirectories("*", SearchOption.TopDirectoryOnly); + } + + // Returns an array of strongly typed FileSystemInfo entries in the path with the + // given search criteria (ie, "*.txt"). + public FileSystemInfo[] GetFileSystemInfos(String searchPattern) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.EndContractBlock(); + + return InternalGetFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly); + } + + // Returns an array of strongly typed FileSystemInfo entries in the path with the + // given search criteria (ie, "*.txt"). + public FileSystemInfo[] GetFileSystemInfos(String searchPattern, SearchOption searchOption) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.EndContractBlock(); + + return InternalGetFileSystemInfos(searchPattern, searchOption); + } + + // Returns an array of strongly typed FileSystemInfo entries in the path with the + // given search criteria (ie, "*.txt"). + private FileSystemInfo[] InternalGetFileSystemInfos(String searchPattern, SearchOption searchOption) + { + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + IEnumerable<FileSystemInfo> enble = FileSystemEnumerableFactory.CreateFileSystemInfoIterator(FullPath, OriginalPath, searchPattern, searchOption); + List<FileSystemInfo> fileList = new List<FileSystemInfo>(enble); + return fileList.ToArray(); + } + + // Returns an array of strongly typed FileSystemInfo entries which will contain a listing + // of all the files and directories. + public FileSystemInfo[] GetFileSystemInfos() + { + return InternalGetFileSystemInfos("*", SearchOption.TopDirectoryOnly); + } + + // Returns an array of Directories in the current DirectoryInfo matching the + // given search criteria (ie, "System*" could match the System & System32 + // directories). + public DirectoryInfo[] GetDirectories(String searchPattern) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.EndContractBlock(); + + return InternalGetDirectories(searchPattern, SearchOption.TopDirectoryOnly); + } + + // Returns an array of Directories in the current DirectoryInfo matching the + // given search criteria (ie, "System*" could match the System & System32 + // directories). + public DirectoryInfo[] GetDirectories(String searchPattern, SearchOption searchOption) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.EndContractBlock(); + + return InternalGetDirectories(searchPattern, searchOption); + } + + // Returns an array of Directories in the current DirectoryInfo matching the + // given search criteria (ie, "System*" could match the System & System32 + // directories). + private DirectoryInfo[] InternalGetDirectories(String searchPattern, SearchOption searchOption) + { + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + IEnumerable<DirectoryInfo> enble = FileSystemEnumerableFactory.CreateDirectoryInfoIterator(FullPath, OriginalPath, searchPattern, searchOption); + List<DirectoryInfo> fileList = new List<DirectoryInfo>(enble); + return fileList.ToArray(); + } + + public IEnumerable<DirectoryInfo> EnumerateDirectories() + { + return InternalEnumerateDirectories("*", SearchOption.TopDirectoryOnly); + } + + public IEnumerable<DirectoryInfo> EnumerateDirectories(String searchPattern) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.EndContractBlock(); + + return InternalEnumerateDirectories(searchPattern, SearchOption.TopDirectoryOnly); + } + + public IEnumerable<DirectoryInfo> EnumerateDirectories(String searchPattern, SearchOption searchOption) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.EndContractBlock(); + + return InternalEnumerateDirectories(searchPattern, searchOption); + } + + private IEnumerable<DirectoryInfo> InternalEnumerateDirectories(String searchPattern, SearchOption searchOption) + { + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + return FileSystemEnumerableFactory.CreateDirectoryInfoIterator(FullPath, OriginalPath, searchPattern, searchOption); + } + + public IEnumerable<FileInfo> EnumerateFiles() + { + return InternalEnumerateFiles("*", SearchOption.TopDirectoryOnly); + } + + public IEnumerable<FileInfo> EnumerateFiles(String searchPattern) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.EndContractBlock(); + + return InternalEnumerateFiles(searchPattern, SearchOption.TopDirectoryOnly); + } + + public IEnumerable<FileInfo> EnumerateFiles(String searchPattern, SearchOption searchOption) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.EndContractBlock(); + + return InternalEnumerateFiles(searchPattern, searchOption); + } + + private IEnumerable<FileInfo> InternalEnumerateFiles(String searchPattern, SearchOption searchOption) + { + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + return FileSystemEnumerableFactory.CreateFileInfoIterator(FullPath, OriginalPath, searchPattern, searchOption); + } + + public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos() + { + return InternalEnumerateFileSystemInfos("*", SearchOption.TopDirectoryOnly); + } + + public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(String searchPattern) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + Contract.EndContractBlock(); + + return InternalEnumerateFileSystemInfos(searchPattern, SearchOption.TopDirectoryOnly); + } + + public IEnumerable<FileSystemInfo> EnumerateFileSystemInfos(String searchPattern, SearchOption searchOption) + { + if (searchPattern == null) + throw new ArgumentNullException("searchPattern"); + if ((searchOption != SearchOption.TopDirectoryOnly) && (searchOption != SearchOption.AllDirectories)) + throw new ArgumentOutOfRangeException("searchOption", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + Contract.EndContractBlock(); + + return InternalEnumerateFileSystemInfos(searchPattern, searchOption); + } + + private IEnumerable<FileSystemInfo> InternalEnumerateFileSystemInfos(String searchPattern, SearchOption searchOption) + { + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + + return FileSystemEnumerableFactory.CreateFileSystemInfoIterator(FullPath, OriginalPath, searchPattern, searchOption); + } + + // Returns the root portion of the given path. The resulting string + // consists of those rightmost characters of the path that constitute the + // root of the path. Possible patterns for the resulting string are: An + // empty string (a relative path on the current drive), "\" (an absolute + // path on the current drive), "X:" (a relative path on a given drive, + // where X is the drive letter), "X:\" (an absolute path on a given drive), + // and "\\server\share" (a UNC path for a given server and share name). + // The resulting string is null if path is null. + // + + public DirectoryInfo Root { + [System.Security.SecuritySafeCritical] + get + { + String demandPath; + int rootLength = Path.GetRootLength(FullPath); + String rootPath = FullPath.Substring(0, rootLength); + demandPath = Directory.GetDemandDir(rootPath, true); + +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPath); + sourceState.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { demandPath }, false, false).Demand(); +#endif + return new DirectoryInfo(rootPath); + } + } + + [System.Security.SecuritySafeCritical] + public void MoveTo(String destDirName) { + if (destDirName==null) + throw new ArgumentNullException("destDirName"); + if (destDirName.Length==0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destDirName"); + Contract.EndContractBlock(); + +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Write | FileSecurityStateAccess.Read, DisplayPath, Directory.GetDemandDir(FullPath, true)); + sourceState.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, demandDir, false, false).Demand(); +#endif + String fullDestDirName = Path.GetFullPathInternal(destDirName); + String demandPath; + if (!fullDestDirName.EndsWith(Path.DirectorySeparatorChar)) + fullDestDirName = fullDestDirName + Path.DirectorySeparatorChar; + + demandPath = fullDestDirName + '.'; + + // Demand read & write permission to destination. The reason is + // we hand back a DirectoryInfo to the destination that would allow + // you to read a directory listing from that directory. Sure, you + // had the ability to read the file contents in the old location, + // but you technically also need read permissions to the new + // location as well, and write is not a true superset of read. +#if FEATURE_CORECLR + FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Write, destDirName, demandPath); + destState.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, demandPath).Demand(); +#endif + + String fullSourcePath; + if (FullPath.EndsWith(Path.DirectorySeparatorChar)) + fullSourcePath = FullPath; + else + fullSourcePath = FullPath + Path.DirectorySeparatorChar; + + if (String.Compare(fullSourcePath, fullDestDirName, StringComparison.OrdinalIgnoreCase) == 0) + throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustBeDifferent")); + + String sourceRoot = Path.GetPathRoot(fullSourcePath); + String destinationRoot = Path.GetPathRoot(fullDestDirName); + + if (String.Compare(sourceRoot, destinationRoot, StringComparison.OrdinalIgnoreCase) != 0) + throw new IOException(Environment.GetResourceString("IO.IO_SourceDestMustHaveSameRoot")); + + if (!Win32Native.MoveFile(FullPath, destDirName)) + { + int hr = Marshal.GetLastWin32Error(); + if (hr == Win32Native.ERROR_FILE_NOT_FOUND) // A dubious error code + { + hr = Win32Native.ERROR_PATH_NOT_FOUND; + __Error.WinIOError(hr, DisplayPath); + } + + if (hr == Win32Native.ERROR_ACCESS_DENIED) // We did this for Win9x. We can't change it for backcomp. + throw new IOException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", DisplayPath)); + + __Error.WinIOError(hr,String.Empty); + } + FullPath = fullDestDirName; + OriginalPath = destDirName; + DisplayPath = GetDisplayName(OriginalPath, FullPath); + demandDir = new String[] { Directory.GetDemandDir(FullPath, true) }; + + // Flush any cached information about the directory. + _dataInitialised = -1; + } + + [System.Security.SecuritySafeCritical] + public override void Delete() + { + Directory.Delete(FullPath, OriginalPath, false, true); + } + + [System.Security.SecuritySafeCritical] + public void Delete(bool recursive) + { + Directory.Delete(FullPath, OriginalPath, recursive, true); + } + + // Returns the fully qualified path + public override String ToString() + { + return DisplayPath; + } + + private static String GetDisplayName(String originalPath, String fullPath) + { + Contract.Assert(originalPath != null); + Contract.Assert(fullPath != null); + + String displayName = ""; + + // Special case "<DriveLetter>:" to point to "<CurrentDirectory>" instead + if ((originalPath.Length == 2) && (originalPath[1] == ':')) + { + displayName = "."; + } + else + { +#if FEATURE_CORECLR + displayName = GetDirName(fullPath); +#else + displayName = originalPath; +#endif + } + return displayName; + } + + private static String GetDirName(String fullPath) + { + Contract.Assert(fullPath != null); + + String dirName = null; + if (fullPath.Length > 3) + { + String s = fullPath; + if (fullPath.EndsWith(Path.DirectorySeparatorChar)) + { + s = fullPath.Substring(0, fullPath.Length - 1); + } + dirName = Path.GetFileName(s); + } + else + { + dirName = fullPath; // For rooted paths, like "c:\" + } + return dirName; + } + + } +} + diff --git a/src/mscorlib/src/System/IO/DirectoryNotFoundException.cs b/src/mscorlib/src/System/IO/DirectoryNotFoundException.cs new file mode 100644 index 0000000000..09d7e7d4e7 --- /dev/null +++ b/src/mscorlib/src/System/IO/DirectoryNotFoundException.cs @@ -0,0 +1,46 @@ +// 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: Exception for accessing a path that doesn't exist. +** +** +===========================================================*/ +using System; +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.InteropServices.ComVisible(true)] + public class DirectoryNotFoundException : IOException { + public DirectoryNotFoundException() + : base(Environment.GetResourceString("Arg_DirectoryNotFoundException")) { + SetErrorCode(__HResults.COR_E_DIRECTORYNOTFOUND); + } + + public DirectoryNotFoundException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_DIRECTORYNOTFOUND); + } + + public DirectoryNotFoundException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_DIRECTORYNOTFOUND); + } + + protected DirectoryNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { + } + } +} diff --git a/src/mscorlib/src/System/IO/DriveInfo.cs b/src/mscorlib/src/System/IO/DriveInfo.cs new file mode 100644 index 0000000000..be75e8979d --- /dev/null +++ b/src/mscorlib/src/System/IO/DriveInfo.cs @@ -0,0 +1,281 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Exposes routines for exploring a drive. +** +** +===========================================================*/ + +using System; +using System.Text; +using System.Runtime.InteropServices; +using Microsoft.Win32; +using System.Security.Permissions; +using System.Runtime.Serialization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; + +namespace System.IO +{ + // Matches Win32's DRIVE_XXX #defines from winbase.h + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + public enum DriveType + { + Unknown = 0, + NoRootDirectory = 1, + Removable = 2, + Fixed = 3, + Network = 4, + CDRom = 5, + Ram = 6 + } + + // Ideally we'll get a better security permission, but possibly + // not for Whidbey. +#if FEATURE_SERIALIZATION + [Serializable] +#endif + [ComVisible(true)] + public sealed class DriveInfo +#if FEATURE_SERIALIZATION + : ISerializable +#endif + { + private String _name; + + private const String NameField = "_name"; // For serialization + + [System.Security.SecuritySafeCritical] // auto-generated + public DriveInfo(String driveName) + { + if (driveName == null) + throw new ArgumentNullException("driveName"); + Contract.EndContractBlock(); + if (driveName.Length == 1) + _name = driveName + ":\\"; + else { + // GetPathRoot does not check all invalid characters + Path.CheckInvalidPathChars(driveName); + _name = Path.GetPathRoot(driveName); + // Disallow null or empty drive letters and UNC paths + if (_name == null || _name.Length == 0 || _name.StartsWith("\\\\", StringComparison.Ordinal)) + throw new ArgumentException(Environment.GetResourceString("Arg_MustBeDriveLetterOrRootDir")); + } + // 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 = _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. + // On a Unix platform, perhaps this should be a device name with + // a partition like /dev/hdc0, or possibly a mount point. + char letter = driveName[0]; + if (!((letter >= 'A' && letter <= 'Z') || (letter >= 'a' && letter <= 'z'))) + throw new ArgumentException(Environment.GetResourceString("Arg_MustBeDriveLetterOrRootDir")); + + // Now do a security check. + String demandPath = _name + '.'; + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPath).Demand(); + } + + [System.Security.SecurityCritical] // auto-generated + private DriveInfo(SerializationInfo info, StreamingContext context) + { + // Need to add in a security check here once it has been spec'ed. + _name = (String) info.GetValue(NameField, typeof(String)); + + // Now do a security check. + String demandPath = _name + '.'; + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPath).Demand(); + } + + public String Name { + get { return _name; } + } + + public DriveType DriveType { + [System.Security.SecuritySafeCritical] // auto-generated + get { + // GetDriveType can't fail + return (DriveType) Win32Native.GetDriveType(Name); + } + } + + public String DriveFormat { + [System.Security.SecuritySafeCritical] // auto-generated + get { + const int volNameLen = 50; + StringBuilder volumeName = new StringBuilder(volNameLen); + const int fileSystemNameLen = 50; + StringBuilder fileSystemName = new StringBuilder(fileSystemNameLen); + int serialNumber, maxFileNameLen, fileSystemFlags; + + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + bool r = Win32Native.GetVolumeInformation(Name, volumeName, volNameLen, out serialNumber, out maxFileNameLen, out fileSystemFlags, fileSystemName, fileSystemNameLen); + if (!r) { + int errorCode = Marshal.GetLastWin32Error(); + __Error.WinIODriveError(Name, errorCode); + } + } + finally { + Win32Native.SetErrorMode(oldMode); + } + return fileSystemName.ToString(); + } + } + + public bool IsReady { + [System.Security.SecuritySafeCritical] // auto-generated + get { + return Directory.InternalExists(Name); + } + } + + public long AvailableFreeSpace { + [System.Security.SecuritySafeCritical] // auto-generated + get { + long userBytes, totalBytes, freeBytes; + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + bool r = Win32Native.GetDiskFreeSpaceEx(Name, out userBytes, out totalBytes, out freeBytes); + if (!r) + __Error.WinIODriveError(Name); + } + finally { + Win32Native.SetErrorMode(oldMode); + } + return userBytes; + } + } + + public long TotalFreeSpace { + [System.Security.SecuritySafeCritical] // auto-generated + get { + long userBytes, totalBytes, freeBytes; + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + bool r = Win32Native.GetDiskFreeSpaceEx(Name, out userBytes, out totalBytes, out freeBytes); + if (!r) + __Error.WinIODriveError(Name); + } + finally { + Win32Native.SetErrorMode(oldMode); + } + return freeBytes; + } + } + + public long TotalSize { + [System.Security.SecuritySafeCritical] // auto-generated + get { + // Don't cache this, to handle variable sized floppy drives + // or other various removable media drives. + long userBytes, totalBytes, freeBytes; + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + bool r = Win32Native.GetDiskFreeSpaceEx(Name, out userBytes, out totalBytes, out freeBytes); + if (!r) + __Error.WinIODriveError(Name); + } + finally { + Win32Native.SetErrorMode(oldMode); + } + return totalBytes; + } + } + + public static DriveInfo[] GetDrives() + { + // Directory.GetLogicalDrives demands unmanaged code permission + String[] drives = Directory.GetLogicalDrives(); + DriveInfo[] di = new DriveInfo[drives.Length]; + for(int i=0; i<drives.Length; i++) + di[i] = new DriveInfo(drives[i]); + return di; + } + + public DirectoryInfo RootDirectory { + get { + return new DirectoryInfo(Name); + } + } + + // Null is a valid volume label. + public String VolumeLabel { + [System.Security.SecuritySafeCritical] // auto-generated + get { + // NTFS uses a limit of 32 characters for the volume label, + // as of Windows Server 2003. + const int volNameLen = 50; + StringBuilder volumeName = new StringBuilder(volNameLen); + const int fileSystemNameLen = 50; + StringBuilder fileSystemName = new StringBuilder(fileSystemNameLen); + int serialNumber, maxFileNameLen, fileSystemFlags; + + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + bool r = Win32Native.GetVolumeInformation(Name, volumeName, volNameLen, out serialNumber, out maxFileNameLen, out fileSystemFlags, fileSystemName, fileSystemNameLen); + if (!r) { + int errorCode = Marshal.GetLastWin32Error(); + // Win9x appears to return ERROR_INVALID_DATA when a + // drive doesn't exist. + if (errorCode == Win32Native.ERROR_INVALID_DATA) + errorCode = Win32Native.ERROR_INVALID_DRIVE; + __Error.WinIODriveError(Name, errorCode); + } + } + finally { + Win32Native.SetErrorMode(oldMode); + } + return volumeName.ToString(); + } + [System.Security.SecuritySafeCritical] // auto-generated + set { + String demandPath = _name + '.'; + new FileIOPermission(FileIOPermissionAccess.Write, demandPath).Demand(); + + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + bool r = Win32Native.SetVolumeLabel(Name, value); + if (!r) { + int errorCode = Marshal.GetLastWin32Error(); + // Provide better message + if (errorCode == Win32Native.ERROR_ACCESS_DENIED) + throw new UnauthorizedAccessException(Environment.GetResourceString("InvalidOperation_SetVolumeLabelFailed")); + __Error.WinIODriveError(Name, errorCode); + } + } + finally { + Win32Native.SetErrorMode(oldMode); + } + } + } + + public override String ToString() + { + return Name; + } + +#if FEATURE_SERIALIZATION + /// <internalonly/> + [System.Security.SecurityCritical] + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + // No need for an additional security check - everything is public. + info.AddValue(NameField, _name, typeof(String)); + } +#endif + + } +} diff --git a/src/mscorlib/src/System/IO/DriveNotFoundException.cs b/src/mscorlib/src/System/IO/DriveNotFoundException.cs new file mode 100644 index 0000000000..04155bcc0e --- /dev/null +++ b/src/mscorlib/src/System/IO/DriveNotFoundException.cs @@ -0,0 +1,40 @@ +// 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: Exception for accessing a drive that is not available. +// +// +//============================================================ +using System; +using System.Runtime.Serialization; + +namespace System.IO { + + //Thrown when trying to access a drive that is not availabe. + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class DriveNotFoundException : IOException { + public DriveNotFoundException() + : base(Environment.GetResourceString("Arg_DriveNotFoundException")) { + SetErrorCode(__HResults.COR_E_DIRECTORYNOTFOUND); + } + + public DriveNotFoundException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_DIRECTORYNOTFOUND); + } + + public DriveNotFoundException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_DIRECTORYNOTFOUND); + } + + protected DriveNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { + } + } +} diff --git a/src/mscorlib/src/System/IO/EndOfStreamException.cs b/src/mscorlib/src/System/IO/EndOfStreamException.cs new file mode 100644 index 0000000000..60f5109a0f --- /dev/null +++ b/src/mscorlib/src/System/IO/EndOfStreamException.cs @@ -0,0 +1,43 @@ +// 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: Exception to be thrown when reading past end-of-file. +** +** +===========================================================*/ + +using System; +using System.Runtime.Serialization; + +namespace System.IO { + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class EndOfStreamException : IOException + { + public EndOfStreamException() + : base(Environment.GetResourceString("Arg_EndOfStreamException")) { + SetErrorCode(__HResults.COR_E_ENDOFSTREAM); + } + + public EndOfStreamException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_ENDOFSTREAM); + } + + public EndOfStreamException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_ENDOFSTREAM); + } + + protected EndOfStreamException(SerializationInfo info, StreamingContext context) : base (info, context) { + } + } + +} diff --git a/src/mscorlib/src/System/IO/File.cs b/src/mscorlib/src/System/IO/File.cs new file mode 100644 index 0000000000..cfcb469bc3 --- /dev/null +++ b/src/mscorlib/src/System/IO/File.cs @@ -0,0 +1,1255 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: A collection of methods for manipulating Files. +** +** April 09,2000 (some design refactorization) +** +===========================================================*/ + +using System; +using System.Security.Permissions; +using PermissionSet = System.Security.PermissionSet; +using Win32Native = Microsoft.Win32.Win32Native; +using System.Runtime.InteropServices; +using System.Security; +#if FEATURE_MACL +using System.Security.AccessControl; +#endif +using System.Text; +using Microsoft.Win32.SafeHandles; +using System.Collections.Generic; +using System.Globalization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; + +namespace System.IO { + // Class for creating FileStream objects, and some basic file management + // routines such as Delete, etc. + [ComVisible(true)] + public static class File + { + private const int GetFileExInfoStandard = 0; + + public static StreamReader OpenText(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + return new StreamReader(path); + } + + public static StreamWriter CreateText(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + return new StreamWriter(path,false); + } + + public static StreamWriter AppendText(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + return new StreamWriter(path,true); + } + + + // Copies an existing file to a new file. An exception is raised if the + // destination file already exists. Use the + // Copy(String, String, boolean) method to allow + // overwriting an existing file. + // + // The caller must have certain FileIOPermissions. The caller must have + // Read permission to sourceFileName and Create + // and Write permissions to destFileName. + // + public static void Copy(String sourceFileName, String destFileName) { + if (sourceFileName == null) + throw new ArgumentNullException("sourceFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (destFileName == null) + throw new ArgumentNullException("destFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (sourceFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "sourceFileName"); + if (destFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destFileName"); + Contract.EndContractBlock(); + + InternalCopy(sourceFileName, destFileName, false, true); + } + + // Copies an existing file to a new file. If overwrite is + // false, then an IOException is thrown if the destination file + // already exists. If overwrite is true, the file is + // overwritten. + // + // The caller must have certain FileIOPermissions. The caller must have + // Read permission to sourceFileName + // and Write permissions to destFileName. + // + public static void Copy(String sourceFileName, String destFileName, bool overwrite) { + if (sourceFileName == null) + throw new ArgumentNullException("sourceFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (destFileName == null) + throw new ArgumentNullException("destFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (sourceFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "sourceFileName"); + if (destFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destFileName"); + Contract.EndContractBlock(); + + InternalCopy(sourceFileName, destFileName, overwrite, true); + } + + [System.Security.SecurityCritical] + internal static void UnsafeCopy(String sourceFileName, String destFileName, bool overwrite) { + if (sourceFileName == null) + throw new ArgumentNullException("sourceFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (destFileName == null) + throw new ArgumentNullException("destFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (sourceFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "sourceFileName"); + if (destFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destFileName"); + Contract.EndContractBlock(); + + InternalCopy(sourceFileName, destFileName, overwrite, false); + } + + /// <devdoc> + /// Note: This returns the fully qualified name of the destination file. + /// </devdoc> + [System.Security.SecuritySafeCritical] + internal static String InternalCopy(String sourceFileName, String destFileName, bool overwrite, bool checkHost) { + Contract.Requires(sourceFileName != null); + Contract.Requires(destFileName != null); + Contract.Requires(sourceFileName.Length > 0); + Contract.Requires(destFileName.Length > 0); + + String fullSourceFileName = Path.GetFullPathInternal(sourceFileName); + String fullDestFileName = Path.GetFullPathInternal(destFileName); + +#if FEATURE_CORECLR + if (checkHost) { + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read, sourceFileName, fullSourceFileName); + FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Write, destFileName, fullDestFileName); + sourceState.EnsureState(); + destState.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullSourceFileName, false, false); + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullDestFileName, false, false); +#endif + + bool r = Win32Native.CopyFile(fullSourceFileName, fullDestFileName, !overwrite); + if (!r) { + // Save Win32 error because subsequent checks will overwrite this HRESULT. + int errorCode = Marshal.GetLastWin32Error(); + String fileName = destFileName; + + if (errorCode != Win32Native.ERROR_FILE_EXISTS) { + // For a number of error codes (sharing violation, path + // not found, etc) we don't know if the problem was with + // the source or dest file. Try reading the source file. + using(SafeFileHandle handle = Win32Native.UnsafeCreateFile(fullSourceFileName, FileStream.GENERIC_READ, FileShare.Read, null, FileMode.Open, 0, IntPtr.Zero)) { + if (handle.IsInvalid) + fileName = sourceFileName; + } + + if (errorCode == Win32Native.ERROR_ACCESS_DENIED) { + if (Directory.InternalExists(fullDestFileName)) + throw new IOException(Environment.GetResourceString("Arg_FileIsDirectory_Name", destFileName), Win32Native.ERROR_ACCESS_DENIED, fullDestFileName); + } + } + + __Error.WinIOError(errorCode, fileName); + } + + return fullDestFileName; + } + + + // Creates a file in a particular path. If the file exists, it is replaced. + // The file is opened with ReadWrite accessand cannot be opened by another + // application until it has been closed. An IOException is thrown if the + // directory specified doesn't exist. + // + // Your application must have Create, Read, and Write permissions to + // the file. + // + public static FileStream Create(String path) { + return Create(path, FileStream.DefaultBufferSize); + } + + // Creates a file in a particular path. If the file exists, it is replaced. + // The file is opened with ReadWrite access and cannot be opened by another + // application until it has been closed. An IOException is thrown if the + // directory specified doesn't exist. + // + // Your application must have Create, Read, and Write permissions to + // the file. + // + public static FileStream Create(String path, int bufferSize) { + return new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, bufferSize); + } + + public static FileStream Create(String path, int bufferSize, FileOptions options) { + return new FileStream(path, FileMode.Create, FileAccess.ReadWrite, + FileShare.None, bufferSize, options); + } + +#if FEATURE_MACL + public static FileStream Create(String path, int bufferSize, FileOptions options, FileSecurity fileSecurity) { + return new FileStream(path, FileMode.Create, FileSystemRights.Read | FileSystemRights.Write, + FileShare.None, bufferSize, options, fileSecurity); + } +#endif + + // Deletes a file. The file specified by the designated path is deleted. + // If the file does not exist, Delete succeeds without throwing + // an exception. + // + // On NT, Delete will fail for a file that is open for normal I/O + // or a file that is memory mapped. + // + // Your application must have Delete permission to the target file. + // + [System.Security.SecuritySafeCritical] + public static void Delete(String path) { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + InternalDelete(path, true); + } + + [System.Security.SecurityCritical] + internal static void UnsafeDelete(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + InternalDelete(path, false); + } + + [System.Security.SecurityCritical] + internal static void InternalDelete(String path, bool checkHost) + { + String fullPath = Path.GetFullPathInternal(path); + +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, path, fullPath); + state.EnsureState(); + } +#else + // For security check, path should be resolved to an absolute path. + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullPath, false, false); + +#endif + bool r = Win32Native.DeleteFile(fullPath); + if (!r) { + int hr = Marshal.GetLastWin32Error(); + if (hr==Win32Native.ERROR_FILE_NOT_FOUND) + return; + else + __Error.WinIOError(hr, fullPath); + } + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public static void Decrypt(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + String fullPath = Path.GetFullPathInternal(path); + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, fullPath, false, false); + + bool r = Win32Native.DecryptFile(fullPath, 0); + if (!r) { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == Win32Native.ERROR_ACCESS_DENIED) { + // Check to see if the file system is not NTFS. If so, + // throw a different exception. + DriveInfo di = new DriveInfo(Path.GetPathRoot(fullPath)); + if (!String.Equals("NTFS", di.DriveFormat)) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_EncryptionNeedsNTFS")); + } + __Error.WinIOError(errorCode, fullPath); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void Encrypt(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + String fullPath = Path.GetFullPathInternal(path); + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, fullPath, false, false); + + bool r = Win32Native.EncryptFile(fullPath); + if (!r) { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == Win32Native.ERROR_ACCESS_DENIED) { + // Check to see if the file system is not NTFS. If so, + // throw a different exception. + DriveInfo di = new DriveInfo(Path.GetPathRoot(fullPath)); + if (!String.Equals("NTFS", di.DriveFormat)) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_EncryptionNeedsNTFS")); + } + __Error.WinIOError(errorCode, fullPath); + } + } + + // Tests if a file exists. The result is true if the file + // given by the specified path exists; otherwise, the result is + // false. Note that if path describes a directory, + // Exists will return true. + // + // Your application must have Read permission for the target directory. + // + [System.Security.SecuritySafeCritical] + public static bool Exists(String path) + { + return InternalExistsHelper(path, true); + } + + [System.Security.SecurityCritical] + internal static bool UnsafeExists(String path) + { + return InternalExistsHelper(path, false); + } + + [System.Security.SecurityCritical] + private static bool InternalExistsHelper(String path, bool checkHost) + { + try + { + if (path == null) + return false; + if (path.Length == 0) + return false; + + path = Path.GetFullPathInternal(path); + // After normalizing, check whether path ends in directory separator. + // Otherwise, FillAttributeInfo removes it and we may return a false positive. + // GetFullPathInternal should never return null + Contract.Assert(path != null, "File.Exists: GetFullPathInternal returned null"); + if (path.Length > 0 && Path.IsDirectorySeparator(path[path.Length - 1])) + { + return false; + } + +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, path); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, path, false, false); +#endif + + return InternalExists(path); + } + catch (ArgumentException) { } + catch (NotSupportedException) { } // Security can throw this on ":" + catch (SecurityException) { } + catch (IOException) { } + catch (UnauthorizedAccessException) { } + + return false; + } + + [System.Security.SecurityCritical] // auto-generated + internal static bool InternalExists(String path) { + Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + int dataInitialised = FillAttributeInfo(path, ref data, false, true); + + return (dataInitialised == 0) && (data.fileAttributes != -1) + && ((data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) == 0); + } + + public static FileStream Open(String path, FileMode mode) { + return Open(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.None); + } + + public static FileStream Open(String path, FileMode mode, FileAccess access) { + return Open(path,mode, access, FileShare.None); + } + + public static FileStream Open(String path, FileMode mode, FileAccess access, FileShare share) { + return new FileStream(path, mode, access, share); + } + + public static void SetCreationTime(String path, DateTime creationTime) + { + SetCreationTimeUtc(path, creationTime.ToUniversalTime()); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe static void SetCreationTimeUtc(String path, DateTime creationTimeUtc) + { + SafeFileHandle handle; + using(OpenFile(path, FileAccess.Write, out handle)) { + Win32Native.FILE_TIME fileTime = new Win32Native.FILE_TIME(creationTimeUtc.ToFileTimeUtc()); + bool r = Win32Native.SetFileTime(handle, &fileTime, null, null); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + __Error.WinIOError(errorCode, path); + } + } + } + + [System.Security.SecuritySafeCritical] + public static DateTime GetCreationTime(String path) + { + return InternalGetCreationTimeUtc(path, true).ToLocalTime(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static DateTime GetCreationTimeUtc(String path) + { + return InternalGetCreationTimeUtc(path, false); // this API isn't exposed in Silverlight + } + + [System.Security.SecurityCritical] + private static DateTime InternalGetCreationTimeUtc(String path, bool checkHost) + { + String fullPath = Path.GetFullPathInternal(path); +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, fullPath); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); +#endif + + Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + int dataInitialised = FillAttributeInfo(fullPath, ref data, false, false); + if (dataInitialised != 0) + __Error.WinIOError(dataInitialised, fullPath); + + long dt = ((long)(data.ftCreationTimeHigh) << 32) | ((long)data.ftCreationTimeLow); + return DateTime.FromFileTimeUtc(dt); + } + + public static void SetLastAccessTime(String path, DateTime lastAccessTime) + { + SetLastAccessTimeUtc(path, lastAccessTime.ToUniversalTime()); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe static void SetLastAccessTimeUtc(String path, DateTime lastAccessTimeUtc) + { + SafeFileHandle handle; + using(OpenFile(path, FileAccess.Write, out handle)) { + Win32Native.FILE_TIME fileTime = new Win32Native.FILE_TIME(lastAccessTimeUtc.ToFileTimeUtc()); + bool r = Win32Native.SetFileTime(handle, null, &fileTime, null); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + __Error.WinIOError(errorCode, path); + } + } + } + + [System.Security.SecuritySafeCritical] + public static DateTime GetLastAccessTime(String path) + { + return InternalGetLastAccessTimeUtc(path, true).ToLocalTime(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static DateTime GetLastAccessTimeUtc(String path) + { + return InternalGetLastAccessTimeUtc(path, false); // this API isn't exposed in Silverlight + } + + [System.Security.SecurityCritical] + private static DateTime InternalGetLastAccessTimeUtc(String path, bool checkHost) + { + String fullPath = Path.GetFullPathInternal(path); +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, fullPath); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); +#endif + + Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + int dataInitialised = FillAttributeInfo(fullPath, ref data, false, false); + if (dataInitialised != 0) + __Error.WinIOError(dataInitialised, fullPath); + + long dt = ((long)(data.ftLastAccessTimeHigh) << 32) | ((long)data.ftLastAccessTimeLow); + return DateTime.FromFileTimeUtc(dt); + } + + public static void SetLastWriteTime(String path, DateTime lastWriteTime) + { + SetLastWriteTimeUtc(path, lastWriteTime.ToUniversalTime()); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe static void SetLastWriteTimeUtc(String path, DateTime lastWriteTimeUtc) + { + SafeFileHandle handle; + using(OpenFile(path, FileAccess.Write, out handle)) { + Win32Native.FILE_TIME fileTime = new Win32Native.FILE_TIME(lastWriteTimeUtc.ToFileTimeUtc()); + bool r = Win32Native.SetFileTime(handle, null, null, &fileTime); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + __Error.WinIOError(errorCode, path); + } + } + } + + [System.Security.SecuritySafeCritical] + public static DateTime GetLastWriteTime(String path) + { + return InternalGetLastWriteTimeUtc(path, true).ToLocalTime(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static DateTime GetLastWriteTimeUtc(String path) + { + return InternalGetLastWriteTimeUtc(path, false); // this API isn't exposed in Silverlight + } + + [System.Security.SecurityCritical] + private static DateTime InternalGetLastWriteTimeUtc(String path, bool checkHost) + { + String fullPath = Path.GetFullPathInternal(path); +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, fullPath); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); +#endif + + Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + int dataInitialised = FillAttributeInfo(fullPath, ref data, false, false); + if (dataInitialised != 0) + __Error.WinIOError(dataInitialised, fullPath); + + long dt = ((long)data.ftLastWriteTimeHigh << 32) | ((long)data.ftLastWriteTimeLow); + return DateTime.FromFileTimeUtc(dt); + } + + [System.Security.SecuritySafeCritical] + public static FileAttributes GetAttributes(String path) + { + String fullPath = Path.GetFullPathInternal(path); +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, path, fullPath); + state.EnsureState(); +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); +#endif + + Win32Native.WIN32_FILE_ATTRIBUTE_DATA data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + int dataInitialised = FillAttributeInfo(fullPath, ref data, false, true); + if (dataInitialised != 0) + __Error.WinIOError(dataInitialised, fullPath); + + return (FileAttributes) data.fileAttributes; + } + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] + #else + [System.Security.SecuritySafeCritical] + #endif + public static void SetAttributes(String path, FileAttributes fileAttributes) + { + String fullPath = Path.GetFullPathInternal(path); +#if !FEATURE_CORECLR + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullPath, false, false); +#endif + bool r = Win32Native.SetFileAttributes(fullPath, (int) fileAttributes); + if (!r) { + int hr = Marshal.GetLastWin32Error(); + if (hr==ERROR_INVALID_PARAMETER) + throw new ArgumentException(Environment.GetResourceString("Arg_InvalidFileAttrs")); + __Error.WinIOError(hr, fullPath); + } + } + +#if FEATURE_MACL + public static FileSecurity GetAccessControl(String path) + { + return GetAccessControl(path, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + public static FileSecurity GetAccessControl(String path, AccessControlSections includeSections) + { + // Appropriate security check should be done for us by FileSecurity. + return new FileSecurity(path, includeSections); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void SetAccessControl(String path, FileSecurity fileSecurity) + { + if (fileSecurity == null) + throw new ArgumentNullException("fileSecurity"); + Contract.EndContractBlock(); + + String fullPath = Path.GetFullPathInternal(path); + // Appropriate security check should be done for us by FileSecurity. + fileSecurity.Persist(fullPath); + } +#endif + + public static FileStream OpenRead(String path) { + return new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); + } + + + public static FileStream OpenWrite(String path) { + return new FileStream(path, FileMode.OpenOrCreate, + FileAccess.Write, FileShare.None); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static String ReadAllText(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + return InternalReadAllText(path, Encoding.UTF8, true); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static String ReadAllText(String path, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + return InternalReadAllText(path, encoding, true); + } + + [System.Security.SecurityCritical] + internal static String UnsafeReadAllText(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + return InternalReadAllText(path, Encoding.UTF8, false); + } + + [System.Security.SecurityCritical] + private static String InternalReadAllText(String path, Encoding encoding, bool checkHost) + { + Contract.Requires(path != null); + Contract.Requires(encoding != null); + Contract.Requires(path.Length > 0); + + using (StreamReader sr = new StreamReader(path, encoding, true, StreamReader.DefaultBufferSize, checkHost)) + return sr.ReadToEnd(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void WriteAllText(String path, String contents) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllText(path, contents, StreamWriter.UTF8NoBOM, true); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void WriteAllText(String path, String contents, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllText(path, contents, encoding, true); + } + + [System.Security.SecurityCritical] + internal static void UnsafeWriteAllText(String path, String contents) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllText(path, contents, StreamWriter.UTF8NoBOM, false); + } + + [System.Security.SecurityCritical] + private static void InternalWriteAllText(String path, String contents, Encoding encoding, bool checkHost) + { + Contract.Requires(path != null); + Contract.Requires(encoding != null); + Contract.Requires(path.Length > 0); + + using (StreamWriter sw = new StreamWriter(path, false, encoding, StreamWriter.DefaultBufferSize, checkHost)) + sw.Write(contents); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static byte[] ReadAllBytes(String path) + { + return InternalReadAllBytes(path, true); + } + + [System.Security.SecurityCritical] + internal static byte[] UnsafeReadAllBytes(String path) + { + return InternalReadAllBytes(path, false); + } + + + [System.Security.SecurityCritical] + private static byte[] InternalReadAllBytes(String path, bool checkHost) + { + byte[] bytes; + using(FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, + FileStream.DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, checkHost)) { + // Do a blocking read + int index = 0; + long fileLength = fs.Length; + if (fileLength > Int32.MaxValue) + throw new IOException(Environment.GetResourceString("IO.IO_FileTooLong2GB")); + int count = (int) fileLength; + bytes = new byte[count]; + while(count > 0) { + int n = fs.Read(bytes, index, count); + if (n == 0) + __Error.EndOfFile(); + index += n; + count -= n; + } + } + return bytes; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public static void WriteAllBytes(String path, byte[] bytes) + { + if (path == null) + throw new ArgumentNullException("path", Environment.GetResourceString("ArgumentNull_Path")); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + if (bytes == null) + throw new ArgumentNullException("bytes"); + Contract.EndContractBlock(); + + InternalWriteAllBytes(path, bytes, true); + } + + [System.Security.SecurityCritical] + internal static void UnsafeWriteAllBytes(String path, byte[] bytes) + { + if (path == null) + throw new ArgumentNullException("path", Environment.GetResourceString("ArgumentNull_Path")); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + if (bytes == null) + throw new ArgumentNullException("bytes"); + Contract.EndContractBlock(); + + InternalWriteAllBytes(path, bytes, false); + } + + [System.Security.SecurityCritical] + private static void InternalWriteAllBytes(String path, byte[] bytes, bool checkHost) + { + Contract.Requires(path != null); + Contract.Requires(path.Length != 0); + Contract.Requires(bytes != null); + + using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, + FileStream.DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, checkHost)) + { + fs.Write(bytes, 0, bytes.Length); + } + } + + public static String[] ReadAllLines(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + return InternalReadAllLines(path, Encoding.UTF8); + } + + public static String[] ReadAllLines(String path, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + return InternalReadAllLines(path, encoding); + } + + private static String[] InternalReadAllLines(String path, Encoding encoding) + { + Contract.Requires(path != null); + Contract.Requires(encoding != null); + Contract.Requires(path.Length != 0); + + String line; + List<String> lines = new List<String>(); + + using (StreamReader sr = new StreamReader(path, encoding)) + while ((line = sr.ReadLine()) != null) + lines.Add(line); + + return lines.ToArray(); + } + + public static IEnumerable<String> ReadLines(String path) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"), "path"); + Contract.EndContractBlock(); + + return ReadLinesIterator.CreateIterator(path, Encoding.UTF8); + } + + public static IEnumerable<String> ReadLines(String path, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath"), "path"); + Contract.EndContractBlock(); + + return ReadLinesIterator.CreateIterator(path, encoding); + } + + public static void WriteAllLines(String path, String[] contents) + { + if (path == null) + throw new ArgumentNullException("path"); + if (contents == null) + throw new ArgumentNullException("contents"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllLines(new StreamWriter(path, false, StreamWriter.UTF8NoBOM), contents); + } + + public static void WriteAllLines(String path, String[] contents, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (contents == null) + throw new ArgumentNullException("contents"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllLines(new StreamWriter(path, false, encoding), contents); + } + + public static void WriteAllLines(String path, IEnumerable<String> contents) + { + if (path == null) + throw new ArgumentNullException("path"); + if (contents == null) + throw new ArgumentNullException("contents"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllLines(new StreamWriter(path, false, StreamWriter.UTF8NoBOM), contents); + } + + public static void WriteAllLines(String path, IEnumerable<String> contents, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (contents == null) + throw new ArgumentNullException("contents"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllLines(new StreamWriter(path, false, encoding), contents); + } + + private static void InternalWriteAllLines(TextWriter writer, IEnumerable<String> contents) + { + Contract.Requires(writer != null); + Contract.Requires(contents != null); + + using (writer) + { + foreach (String line in contents) + { + writer.WriteLine(line); + } + } + } + + public static void AppendAllText(String path, String contents) + { + if (path == null) + throw new ArgumentNullException("path"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalAppendAllText(path, contents, StreamWriter.UTF8NoBOM); + } + + public static void AppendAllText(String path, String contents, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalAppendAllText(path, contents, encoding); + } + + private static void InternalAppendAllText(String path, String contents, Encoding encoding) + { + Contract.Requires(path != null); + Contract.Requires(encoding != null); + Contract.Requires(path.Length > 0); + + using (StreamWriter sw = new StreamWriter(path, true, encoding)) + sw.Write(contents); + } + + public static void AppendAllLines(String path, IEnumerable<String> contents) + { + if (path == null) + throw new ArgumentNullException("path"); + if (contents == null) + throw new ArgumentNullException("contents"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllLines(new StreamWriter(path, true, StreamWriter.UTF8NoBOM), contents); + } + + public static void AppendAllLines(String path, IEnumerable<String> contents, Encoding encoding) + { + if (path == null) + throw new ArgumentNullException("path"); + if (contents == null) + throw new ArgumentNullException("contents"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + + InternalWriteAllLines(new StreamWriter(path, true, encoding), contents); + } + + // Moves a specified file to a new location and potentially a new file name. + // This method does work across volumes. + // + // The caller must have certain FileIOPermissions. The caller must + // have Read and Write permission to + // sourceFileName and Write + // permissions to destFileName. + // + [System.Security.SecuritySafeCritical] + public static void Move(String sourceFileName, String destFileName) { + InternalMove(sourceFileName, destFileName, true); + } + + [System.Security.SecurityCritical] + internal static void UnsafeMove(String sourceFileName, String destFileName) { + InternalMove(sourceFileName, destFileName, false); + } + + [System.Security.SecurityCritical] + private static void InternalMove(String sourceFileName, String destFileName, bool checkHost) { + if (sourceFileName == null) + throw new ArgumentNullException("sourceFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (destFileName == null) + throw new ArgumentNullException("destFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (sourceFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "sourceFileName"); + if (destFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destFileName"); + Contract.EndContractBlock(); + + String fullSourceFileName = Path.GetFullPathInternal(sourceFileName); + String fullDestFileName = Path.GetFullPathInternal(destFileName); + +#if FEATURE_CORECLR + if (checkHost) { + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Write | FileSecurityStateAccess.Read, sourceFileName, fullSourceFileName); + FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Write, destFileName, fullDestFileName); + sourceState.EnsureState(); + destState.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, fullSourceFileName, false, false); + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullDestFileName, false, false); +#endif + + if (!InternalExists(fullSourceFileName)) + __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, fullSourceFileName); + + if (!Win32Native.MoveFile(fullSourceFileName, fullDestFileName)) + { + __Error.WinIOError(); + } + } + + + public static void Replace(String sourceFileName, String destinationFileName, String destinationBackupFileName) + { + if (sourceFileName == null) + throw new ArgumentNullException("sourceFileName"); + if (destinationFileName == null) + throw new ArgumentNullException("destinationFileName"); + Contract.EndContractBlock(); + + InternalReplace(sourceFileName, destinationFileName, destinationBackupFileName, false); + } + + public static void Replace(String sourceFileName, String destinationFileName, String destinationBackupFileName, bool ignoreMetadataErrors) + { + if (sourceFileName == null) + throw new ArgumentNullException("sourceFileName"); + if (destinationFileName == null) + throw new ArgumentNullException("destinationFileName"); + Contract.EndContractBlock(); + + InternalReplace(sourceFileName, destinationFileName, destinationBackupFileName, ignoreMetadataErrors); + } + + [System.Security.SecuritySafeCritical] + private static void InternalReplace(String sourceFileName, String destinationFileName, String destinationBackupFileName, bool ignoreMetadataErrors) + { + Contract.Requires(sourceFileName != null); + Contract.Requires(destinationFileName != null); + + // Write permission to all three files, read permission to source + // and dest. + String fullSrcPath = Path.GetFullPathInternal(sourceFileName); + String fullDestPath = Path.GetFullPathInternal(destinationFileName); + String fullBackupPath = null; + if (destinationBackupFileName != null) + fullBackupPath = Path.GetFullPathInternal(destinationBackupFileName); + +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read | FileSecurityStateAccess.Write, sourceFileName, fullSrcPath); + FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Read | FileSecurityStateAccess.Write, destinationFileName, fullDestPath); + FileSecurityState backupState = new FileSecurityState(FileSecurityStateAccess.Read | FileSecurityStateAccess.Write, destinationBackupFileName, fullBackupPath); + sourceState.EnsureState(); + destState.EnsureState(); + backupState.EnsureState(); +#else + FileIOPermission perm = new FileIOPermission(FileIOPermissionAccess.Read | FileIOPermissionAccess.Write, new String[] { fullSrcPath, fullDestPath}); + if (destinationBackupFileName != null) + perm.AddPathList(FileIOPermissionAccess.Write, fullBackupPath); + perm.Demand(); +#endif + + int flags = Win32Native.REPLACEFILE_WRITE_THROUGH; + if (ignoreMetadataErrors) + flags |= Win32Native.REPLACEFILE_IGNORE_MERGE_ERRORS; + + bool r = Win32Native.ReplaceFile(fullDestPath, fullSrcPath, fullBackupPath, flags, IntPtr.Zero, IntPtr.Zero); + if (!r) + __Error.WinIOError(); + } + + + // Returns 0 on success, otherwise a Win32 error code. Note that + // classes should use -1 as the uninitialized state for dataInitialized. + [System.Security.SecurityCritical] // auto-generated + internal static int FillAttributeInfo(String path, ref Win32Native.WIN32_FILE_ATTRIBUTE_DATA data, bool tryagain, bool returnErrorOnNotFound) + { + int dataInitialised = 0; + if (tryagain) // someone has a handle to the file open, or other error + { + Win32Native.WIN32_FIND_DATA findData; + findData = new Win32Native.WIN32_FIND_DATA (); + + // Remove trialing slash since this can cause grief to FindFirstFile. You will get an invalid argument error + String tempPath = path.TrimEnd(new char [] {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}); + + // For floppy drives, normally the OS will pop up a dialog saying + // there is no disk in drive A:, please insert one. We don't want that. + // SetErrorMode will let us disable this, but we should set the error + // mode back, since this may have wide-ranging effects. + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + bool error = false; + SafeFindHandle handle = Win32Native.FindFirstFile(tempPath,findData); + try { + if (handle.IsInvalid) { + error = true; + dataInitialised = Marshal.GetLastWin32Error(); + + if (dataInitialised == Win32Native.ERROR_FILE_NOT_FOUND || + dataInitialised == Win32Native.ERROR_PATH_NOT_FOUND || + dataInitialised == Win32Native.ERROR_NOT_READY) // floppy device not ready + { + if (!returnErrorOnNotFound) { + // Return default value for backward compatibility + dataInitialised = 0; + data.fileAttributes = -1; + } + } + return dataInitialised; + } + } + finally { + // Close the Win32 handle + try { + handle.Close(); + } + catch { + // if we're already returning an error, don't throw another one. + if (!error) { + Contract.Assert(false, "File::FillAttributeInfo - FindClose failed!"); + __Error.WinIOError(); + } + } + } + } + finally { + Win32Native.SetErrorMode(oldMode); + } + + // Copy the information to data + data.PopulateFrom(findData); + } + else + { + + // For floppy drives, normally the OS will pop up a dialog saying + // there is no disk in drive A:, please insert one. We don't want that. + // SetErrorMode will let us disable this, but we should set the error + // mode back, since this may have wide-ranging effects. + bool success = false; + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + success = Win32Native.GetFileAttributesEx(path, GetFileExInfoStandard, ref data); + } + finally { + Win32Native.SetErrorMode(oldMode); + } + + if (!success) { + dataInitialised = Marshal.GetLastWin32Error(); + if (dataInitialised != Win32Native.ERROR_FILE_NOT_FOUND && + dataInitialised != Win32Native.ERROR_PATH_NOT_FOUND && + dataInitialised != Win32Native.ERROR_NOT_READY) // floppy device not ready + { + // In case someone latched onto the file. Take the perf hit only for failure + return FillAttributeInfo(path, ref data, true, returnErrorOnNotFound); + } + else { + if (!returnErrorOnNotFound) { + // Return default value for backward compbatibility + dataInitialised = 0; + data.fileAttributes = -1; + } + } + } + } + + return dataInitialised; + } + + [System.Security.SecurityCritical] // auto-generated + private static FileStream OpenFile(String path, FileAccess access, out SafeFileHandle handle) + { + FileStream fs = new FileStream(path, FileMode.Open, access, FileShare.ReadWrite, 1); + handle = fs.SafeFileHandle; + + if (handle.IsInvalid) { + // Return a meaningful error, using the RELATIVE path to + // the file to avoid returning extra information to the caller. + + // NT5 oddity - when trying to open "C:\" as a FileStream, + // we usually get ERROR_PATH_NOT_FOUND from the OS. We should + // probably be consistent w/ every other directory. + int hr = Marshal.GetLastWin32Error(); + String FullPath = Path.GetFullPathInternal(path); + if (hr==__Error.ERROR_PATH_NOT_FOUND && FullPath.Equals(Directory.GetDirectoryRoot(FullPath))) + hr = __Error.ERROR_ACCESS_DENIED; + + + __Error.WinIOError(hr, path); + } + return fs; + } + + + // Defined in WinError.h + private const int ERROR_INVALID_PARAMETER = 87; + private const int ERROR_ACCESS_DENIED = 0x5; + } +} diff --git a/src/mscorlib/src/System/IO/FileAccess.cs b/src/mscorlib/src/System/IO/FileAccess.cs new file mode 100644 index 0000000000..f708a284c1 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileAccess.cs @@ -0,0 +1,42 @@ +// 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. + +/*============================================================ +** +** Enum: FileAccess +** +** +** +** +** Purpose: Enum describing whether you want read and/or write +** permission to a file. +** +** +===========================================================*/ + +using System; + +namespace System.IO { + // Contains constants for specifying the access you want for a file. + // You can have Read, Write or ReadWrite access. + // +[Serializable] +[Flags] +[System.Runtime.InteropServices.ComVisible(true)] + 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/src/mscorlib/src/System/IO/FileAttributes.cs b/src/mscorlib/src/System/IO/FileAttributes.cs new file mode 100644 index 0000000000..19d5f227d7 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileAttributes.cs @@ -0,0 +1,51 @@ +// 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: File attribute flags corresponding to NT's flags. +** +** +===========================================================*/ +using System; + +namespace System.IO { + // File attributes for use with the FileEnumerator class. + // These constants correspond to the constants in WinNT.h. + // +[Serializable] + [Flags] +[System.Runtime.InteropServices.ComVisible(true)] + public enum FileAttributes + { + // From WinNT.h (FILE_ATTRIBUTE_XXX) + ReadOnly = 0x1, + Hidden = 0x2, + System = 0x4, + Directory = 0x10, + Archive = 0x20, + Device = 0x40, + Normal = 0x80, + Temporary = 0x100, + SparseFile = 0x200, + ReparsePoint = 0x400, + Compressed = 0x800, + Offline = 0x1000, + NotContentIndexed = 0x2000, + Encrypted = 0x4000, + +#if !FEATURE_CORECLR +#if FEATURE_COMINTEROP + [System.Runtime.InteropServices.ComVisible(false)] +#endif // FEATURE_COMINTEROP + IntegrityStream = 0x8000, + +#if FEATURE_COMINTEROP + [System.Runtime.InteropServices.ComVisible(false)] +#endif // FEATURE_COMINTEROP + NoScrubData = 0x20000, +#endif + } +} diff --git a/src/mscorlib/src/System/IO/FileInfo.cs b/src/mscorlib/src/System/IO/FileInfo.cs new file mode 100644 index 0000000000..3ab1a5122e --- /dev/null +++ b/src/mscorlib/src/System/IO/FileInfo.cs @@ -0,0 +1,431 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: A collection of methods for manipulating Files. +** +** April 09,2000 (some design refactorization) +** +===========================================================*/ + +using System; +#if FEATURE_MACL +using System.Security.AccessControl; +#endif +using System.Security.Permissions; +using PermissionSet = System.Security.PermissionSet; +using Win32Native = Microsoft.Win32.Win32Native; +using System.Runtime.InteropServices; +using System.Text; +using System.Runtime.Serialization; +using System.Globalization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; + +namespace System.IO { + // Class for creating FileStream objects, and some basic file management + // routines such as Delete, etc. + [Serializable] + [ComVisible(true)] + public sealed class FileInfo: FileSystemInfo + { + private String _name; + +#if FEATURE_CORECLR + // Migrating InheritanceDemands requires this default ctor, so we can annotate it. +#if FEATURE_CORESYSTEM + [System.Security.SecurityCritical] +#else + [System.Security.SecuritySafeCritical] +#endif //FEATURE_CORESYSTEM + private FileInfo(){} + + [System.Security.SecurityCritical] + public static FileInfo UnsafeCreateFileInfo(String fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + Contract.EndContractBlock(); + + FileInfo fi = new FileInfo(); + fi.Init(fileName, false); + return fi; + } +#endif + + [System.Security.SecuritySafeCritical] + public FileInfo(String fileName) + { + if (fileName == null) + throw new ArgumentNullException("fileName"); + Contract.EndContractBlock(); + + Init(fileName, true); + } + + [System.Security.SecurityCritical] + private void Init(String fileName, bool checkHost) + { + OriginalPath = fileName; + // Must fully qualify the path for the security check + String fullPath = Path.GetFullPathInternal(fileName); +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, fileName, fullPath); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Read, fullPath, false, false); +#endif + + _name = Path.GetFileName(fileName); + FullPath = fullPath; + DisplayPath = GetDisplayPath(fileName); + } + + private String GetDisplayPath(String originalPath) + { +#if FEATURE_CORECLR + return Path.GetFileName(originalPath); +#else + return originalPath; +#endif + + } + + [System.Security.SecurityCritical] // auto-generated + private FileInfo(SerializationInfo info, StreamingContext context) : base(info, context) + { +#if !FEATURE_CORECLR + new FileIOPermission(FileIOPermissionAccess.Read, new String[] { FullPath }, false, false).Demand(); +#endif + _name = Path.GetFileName(OriginalPath); + DisplayPath = GetDisplayPath(OriginalPath); + } + +#if FEATURE_CORESYSTEM + [System.Security.SecuritySafeCritical] +#endif //FEATURE_CORESYSTEM + internal FileInfo(String fullPath, bool ignoreThis) + { + Contract.Assert(Path.GetRootLength(fullPath) > 0, "fullPath must be fully qualified!"); + _name = Path.GetFileName(fullPath); + OriginalPath = _name; + FullPath = fullPath; + DisplayPath = _name; + } + + public override String Name { + get { return _name; } + } + + + public long Length { + [System.Security.SecuritySafeCritical] // auto-generated + get { + if (_dataInitialised == -1) + Refresh(); + + if (_dataInitialised != 0) // Refresh was unable to initialise the data + __Error.WinIOError(_dataInitialised, DisplayPath); + + if ((_data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) != 0) + __Error.WinIOError(Win32Native.ERROR_FILE_NOT_FOUND, DisplayPath); + + return ((long)_data.fileSizeHigh) << 32 | ((long)_data.fileSizeLow & 0xFFFFFFFFL); + } + } + + /* Returns the name of the directory that the file is in */ + public String DirectoryName + { + [System.Security.SecuritySafeCritical] + get + { + String directoryName = Path.GetDirectoryName(FullPath); + if (directoryName != null) + { +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, DisplayPath, FullPath); + state.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { directoryName }, false, false).Demand(); +#endif + } + return directoryName; + } + } + + /* Creates an instance of the the parent directory */ + public DirectoryInfo Directory + { + get + { + String dirName = DirectoryName; + if (dirName == null) + return null; + return new DirectoryInfo(dirName); + } + } + + public bool IsReadOnly { + get { + return (Attributes & FileAttributes.ReadOnly) != 0; + } + set { + if (value) + Attributes |= FileAttributes.ReadOnly; + else + Attributes &= ~FileAttributes.ReadOnly; + } + } + +#if FEATURE_MACL + public FileSecurity GetAccessControl() + { + return File.GetAccessControl(FullPath, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + public FileSecurity GetAccessControl(AccessControlSections includeSections) + { + return File.GetAccessControl(FullPath, includeSections); + } + + public void SetAccessControl(FileSecurity fileSecurity) + { + File.SetAccessControl(FullPath, fileSecurity); + } +#endif + + [System.Security.SecuritySafeCritical] // auto-generated + public StreamReader OpenText() + { + return new StreamReader(FullPath, Encoding.UTF8, true, StreamReader.DefaultBufferSize, false); + } + + public StreamWriter CreateText() + { + return new StreamWriter(FullPath,false); + } + + public StreamWriter AppendText() + { + return new StreamWriter(FullPath,true); + } + + + // Copies an existing file to a new file. An exception is raised if the + // destination file already exists. Use the + // Copy(String, String, boolean) method to allow + // overwriting an existing file. + // + // The caller must have certain FileIOPermissions. The caller must have + // Read permission to sourceFileName + // and Write permissions to destFileName. + // + public FileInfo CopyTo(String destFileName) { + if (destFileName == null) + throw new ArgumentNullException("destFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (destFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destFileName"); + Contract.EndContractBlock(); + + destFileName = File.InternalCopy(FullPath, destFileName, false, true); + return new FileInfo(destFileName, false); + } + + + // Copies an existing file to a new file. If overwrite is + // false, then an IOException is thrown if the destination file + // already exists. If overwrite is true, the file is + // overwritten. + // + // The caller must have certain FileIOPermissions. The caller must have + // Read permission to sourceFileName and Create + // and Write permissions to destFileName. + // + public FileInfo CopyTo(String destFileName, bool overwrite) { + if (destFileName == null) + throw new ArgumentNullException("destFileName", Environment.GetResourceString("ArgumentNull_FileName")); + if (destFileName.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destFileName"); + Contract.EndContractBlock(); + + destFileName = File.InternalCopy(FullPath, destFileName, overwrite, true); + return new FileInfo(destFileName, false); + } + + public FileStream Create() { + return File.Create(FullPath); + } + + // Deletes a file. The file specified by the designated path is deleted. + // If the file does not exist, Delete succeeds without throwing + // an exception. + // + // On NT, Delete will fail for a file that is open for normal I/O + // or a file that is memory mapped. On Win95, the file will be + // deleted irregardless of whether the file is being used. + // + // Your application must have Delete permission to the target file. + // + [System.Security.SecuritySafeCritical] + public override void Delete() + { +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, DisplayPath, FullPath); + state.EnsureState(); +#else + // For security check, path should be resolved to an absolute path. + new FileIOPermission(FileIOPermissionAccess.Write, new String[] { FullPath }, false, false).Demand(); +#endif + + bool r = Win32Native.DeleteFile(FullPath); + if (!r) { + int hr = Marshal.GetLastWin32Error(); + if (hr==Win32Native.ERROR_FILE_NOT_FOUND) + return; + else + __Error.WinIOError(hr, DisplayPath); + } + } + + [ComVisible(false)] + public void Decrypt() + { + File.Decrypt(FullPath); + } + + [ComVisible(false)] + public void Encrypt() + { + File.Encrypt(FullPath); + } + + // Tests if the given file exists. The result is true if the file + // given by the specified path exists; otherwise, the result is + // false. + // + // Your application must have Read permission for the target directory. + public override bool Exists { + [System.Security.SecuritySafeCritical] // auto-generated + get { + try { + if (_dataInitialised == -1) + Refresh(); + if (_dataInitialised != 0) { + // Refresh was unable to initialise the data. + // We should normally be throwing an exception here, + // but Exists is supposed to return true or false. + return false; + } + return (_data.fileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY) == 0; + } + catch + { + return false; + } + } + } + + + + + // User must explicitly specify opening a new file or appending to one. + public FileStream Open(FileMode mode) { + return Open(mode, FileAccess.ReadWrite, FileShare.None); + } + + public FileStream Open(FileMode mode, FileAccess access) { + return Open(mode, access, FileShare.None); + } + + public FileStream Open(FileMode mode, FileAccess access, FileShare share) { + return new FileStream(FullPath, mode, access, share); + } + + +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated +#endif + public FileStream OpenRead() + { + return new FileStream(FullPath, FileMode.Open, FileAccess.Read, + FileShare.Read, 4096, false); + } + + + public FileStream OpenWrite() { + return new FileStream(FullPath, FileMode.OpenOrCreate, + FileAccess.Write, FileShare.None); + } + + + + + + + // Moves a given file to a new location and potentially a new file name. + // This method does work across volumes. + // + // The caller must have certain FileIOPermissions. The caller must + // have Read and Write permission to + // sourceFileName and Write + // permissions to destFileName. + // + [System.Security.SecuritySafeCritical] + public void MoveTo(String destFileName) { + if (destFileName==null) + throw new ArgumentNullException("destFileName"); + if (destFileName.Length==0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyFileName"), "destFileName"); + Contract.EndContractBlock(); + + String fullDestFileName = Path.GetFullPathInternal(destFileName); +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Write | FileSecurityStateAccess.Read, DisplayPath, FullPath); + FileSecurityState destState = new FileSecurityState(FileSecurityStateAccess.Write, destFileName, fullDestFileName); + sourceState.EnsureState(); + destState.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.Write | FileIOPermissionAccess.Read, new String[] { FullPath }, false, false).Demand(); + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, fullDestFileName, false, false); +#endif + + if (!Win32Native.MoveFile(FullPath, fullDestFileName)) + __Error.WinIOError(); + FullPath = fullDestFileName; + OriginalPath = destFileName; + _name = Path.GetFileName(fullDestFileName); + DisplayPath = GetDisplayPath(destFileName); + // Flush any cached information about the file. + _dataInitialised = -1; + } + + [ComVisible(false)] + public FileInfo Replace(String destinationFileName, String destinationBackupFileName) + { + return Replace(destinationFileName, destinationBackupFileName, false); + } + + [ComVisible(false)] + public FileInfo Replace(String destinationFileName, String destinationBackupFileName, bool ignoreMetadataErrors) + { + File.Replace(FullPath, destinationFileName, destinationBackupFileName, ignoreMetadataErrors); + return new FileInfo(destinationFileName); + } + + // Returns the display path + public override String ToString() + { + return DisplayPath; + } + } +} diff --git a/src/mscorlib/src/System/IO/FileLoadException.cs b/src/mscorlib/src/System/IO/FileLoadException.cs new file mode 100644 index 0000000000..fabe2613c3 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileLoadException.cs @@ -0,0 +1,191 @@ +// 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: Exception for failure to load a file that was successfully found. +** +** +===========================================================*/ + +using System; +using System.Globalization; +using System.Runtime.Serialization; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Security; +using System.Security.Permissions; +using System.Runtime.Versioning; +using SecurityException = System.Security.SecurityException; + +namespace System.IO { + + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class FileLoadException : IOException { + + private String _fileName; // the name of the file we could not load. + private String _fusionLog; // fusion log (when applicable) + + public FileLoadException() + : base(Environment.GetResourceString("IO.FileLoad")) { + SetErrorCode(__HResults.COR_E_FILELOAD); + } + + public FileLoadException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_FILELOAD); + } + + public FileLoadException(String message, Exception inner) + : base(message, inner) { + SetErrorCode(__HResults.COR_E_FILELOAD); + } + + public FileLoadException(String message, String fileName) : base(message) + { + SetErrorCode(__HResults.COR_E_FILELOAD); + _fileName = fileName; + } + + public FileLoadException(String message, String fileName, Exception inner) + : base(message, inner) { + SetErrorCode(__HResults.COR_E_FILELOAD); + _fileName = fileName; + } + + public override String Message + { + get { + SetMessageField(); + return _message; + } + } + + private void SetMessageField() + { + if (_message == null) + _message = FormatFileLoadExceptionMessage(_fileName, HResult); + } + + public String FileName { + get { return _fileName; } + } + + public override String ToString() + { + String s = GetType().FullName + ": " + Message; + + if (_fileName != null && _fileName.Length != 0) + s += Environment.NewLine + Environment.GetResourceString("IO.FileName_Name", _fileName); + + if (InnerException != null) + s = s + " ---> " + InnerException.ToString(); + + if (StackTrace != null) + s += Environment.NewLine + StackTrace; + +#if FEATURE_FUSION + try + { + if(FusionLog!=null) + { + if (s==null) + s=" "; + s+=Environment.NewLine; + s+=Environment.NewLine; + s+=FusionLog; + } + } + catch(SecurityException) + { + + } +#endif // FEATURE_FUSION + + return s; + } + + protected FileLoadException(SerializationInfo info, StreamingContext context) : base (info, context) { + // Base class constructor will check info != null. + + _fileName = info.GetString("FileLoad_FileName"); + +#if FEATURE_FUSION + try + { + _fusionLog = info.GetString("FileLoad_FusionLog"); + } + catch + { + _fusionLog = null; + } +#endif + } + + private FileLoadException(String fileName, String fusionLog,int hResult) + : base(null) + { + SetErrorCode(hResult); + _fileName = fileName; + _fusionLog=fusionLog; + SetMessageField(); + } + +#if FEATURE_FUSION + public String FusionLog { + [System.Security.SecuritySafeCritical] // auto-generated + [SecurityPermissionAttribute( SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlEvidence | SecurityPermissionFlag.ControlPolicy)] + get { return _fusionLog; } + } +#endif // FEATURE_FUSION + + [System.Security.SecurityCritical] // auto-generated_required + public override void GetObjectData(SerializationInfo info, StreamingContext context) { + // Serialize data for our base classes. base will verify info != null. + base.GetObjectData(info, context); + + // Serialize data for this class + info.AddValue("FileLoad_FileName", _fileName, typeof(String)); + +#if FEATURE_FUSION + try + { + info.AddValue("FileLoad_FusionLog", FusionLog, typeof(String)); + } + catch (SecurityException) + { + } +#endif + } + + [System.Security.SecuritySafeCritical] // auto-generated + internal static String FormatFileLoadExceptionMessage(String fileName, + int hResult) + { + string format = null; + GetFileLoadExceptionMessage(hResult, JitHelpers.GetStringHandleOnStack(ref format)); + + string message = null; + GetMessageForHR(hResult, JitHelpers.GetStringHandleOnStack(ref message)); + + return String.Format(CultureInfo.CurrentCulture, format, fileName, message); + } + + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern void GetFileLoadExceptionMessage(int hResult, StringHandleOnStack retString); + + [System.Security.SecurityCritical] // auto-generated + [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] + [SuppressUnmanagedCodeSecurity] + private static extern void GetMessageForHR(int hresult, StringHandleOnStack retString); + } +} diff --git a/src/mscorlib/src/System/IO/FileMode.cs b/src/mscorlib/src/System/IO/FileMode.cs new file mode 100644 index 0000000000..9b9c2024a7 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileMode.cs @@ -0,0 +1,54 @@ +// 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. + +/*============================================================ +** +** Enum: FileMode +** +** +** +** +** Purpose: Enum describing whether to create a new file or +** open an existing one. +** +** +===========================================================*/ + +using System; + +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. + // + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + 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/src/mscorlib/src/System/IO/FileNotFoundException.cs b/src/mscorlib/src/System/IO/FileNotFoundException.cs new file mode 100644 index 0000000000..933e4fd94c --- /dev/null +++ b/src/mscorlib/src/System/IO/FileNotFoundException.cs @@ -0,0 +1,170 @@ +// 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: Exception for accessing a file that doesn't exist. +** +** +===========================================================*/ + +using System; +using System.Runtime.Serialization; +using System.Security.Permissions; +using SecurityException = System.Security.SecurityException; +using System.Globalization; + +namespace System.IO { + // Thrown when trying to access a file that doesn't exist on disk. + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class FileNotFoundException : IOException { + + private String _fileName; // The name of the file that isn't found. + private String _fusionLog; // fusion log (when applicable) + + public FileNotFoundException() + : base(Environment.GetResourceString("IO.FileNotFound")) { + SetErrorCode(__HResults.COR_E_FILENOTFOUND); + } + + public FileNotFoundException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_FILENOTFOUND); + } + + public FileNotFoundException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_FILENOTFOUND); + } + + public FileNotFoundException(String message, String fileName) : base(message) + { + SetErrorCode(__HResults.COR_E_FILENOTFOUND); + _fileName = fileName; + } + + public FileNotFoundException(String message, String fileName, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_FILENOTFOUND); + _fileName = fileName; + } + + public override String Message + { + get { + SetMessageField(); + return _message; + } + } + + private void SetMessageField() + { + if (_message == null) { + if ((_fileName == null) && + (HResult == System.__HResults.COR_E_EXCEPTION)) + _message = Environment.GetResourceString("IO.FileNotFound"); + + else if( _fileName != null) + _message = FileLoadException.FormatFileLoadExceptionMessage(_fileName, HResult); + } + } + + public String FileName { + get { return _fileName; } + } + + public override String ToString() + { + String s = GetType().FullName + ": " + Message; + + if (_fileName != null && _fileName.Length != 0) + s += Environment.NewLine + Environment.GetResourceString("IO.FileName_Name", _fileName); + + if (InnerException != null) + s = s + " ---> " + InnerException.ToString(); + + if (StackTrace != null) + s += Environment.NewLine + StackTrace; + +#if FEATURE_FUSION + try + { + if(FusionLog!=null) + { + if (s==null) + s=" "; + s+=Environment.NewLine; + s+=Environment.NewLine; + s+=FusionLog; + } + } + catch(SecurityException) + { + + } +#endif + return s; + + } + + protected FileNotFoundException(SerializationInfo info, StreamingContext context) : base (info, context) { + // Base class constructor will check info != null. + + _fileName = info.GetString("FileNotFound_FileName"); +#if FEATURE_FUSION + try + { + _fusionLog = info.GetString("FileNotFound_FusionLog"); + } + catch + { + _fusionLog = null; + } +#endif + } + + private FileNotFoundException(String fileName, String fusionLog,int hResult) + : base(null) + { + SetErrorCode(hResult); + _fileName = fileName; + _fusionLog=fusionLog; + SetMessageField(); + } + +#if FEATURE_FUSION + public String FusionLog { + [System.Security.SecuritySafeCritical] // auto-generated + [SecurityPermissionAttribute( SecurityAction.Demand, Flags = SecurityPermissionFlag.ControlEvidence | SecurityPermissionFlag.ControlPolicy)] + get { return _fusionLog; } + } +#endif + + [System.Security.SecurityCritical] // auto-generated_required + public override void GetObjectData(SerializationInfo info, StreamingContext context) { + // Serialize data for our base classes. base will verify info != null. + base.GetObjectData(info, context); + + // Serialize data for this class + info.AddValue("FileNotFound_FileName", _fileName, typeof(String)); + +#if FEATURE_FUSION + try + { + info.AddValue("FileNotFound_FusionLog", FusionLog, typeof(String)); + } + catch (SecurityException) + { + } +#endif + } + } +} + diff --git a/src/mscorlib/src/System/IO/FileOptions.cs b/src/mscorlib/src/System/IO/FileOptions.cs new file mode 100644 index 0000000000..ffd6ddad90 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileOptions.cs @@ -0,0 +1,47 @@ +// 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. + +/*============================================================ +** +** Enum: FileOptions +** +** +** +** +** Purpose: Additional options to how to create a FileStream. +** Exposes the more obscure CreateFile functionality. +** +** +===========================================================*/ + +using System; +using System.Runtime.InteropServices; + +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. + [Serializable] + [Flags] + [ComVisible(true)] + 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/src/mscorlib/src/System/IO/FileSecurityState.cs b/src/mscorlib/src/System/IO/FileSecurityState.cs new file mode 100644 index 0000000000..249848ac02 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileSecurityState.cs @@ -0,0 +1,133 @@ +// 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. + +/*============================================================ +** +** Enum: FileSecurityState +** +** +** +** +** Purpose: Determines whether file system access is safe +** +** +===========================================================*/ + +using System; +using System.Diagnostics.Contracts; +using System.IO; +using System.Security; +using System.Security.Permissions; + +namespace System.IO +{ + [SecurityCritical] + [System.Runtime.CompilerServices.FriendAccessAllowed] + internal class FileSecurityState : SecurityState + { +#if !PLATFORM_UNIX + private static readonly char[] m_illegalCharacters = { '?', '*' }; +#endif // !PLATFORM_UNIX + + private FileSecurityStateAccess m_access; + private String m_userPath; + private String m_canonicalizedPath; + + // default ctor needed for security rule consistency + [SecurityCritical] + private FileSecurityState() + { + } + + internal FileSecurityState(FileSecurityStateAccess access, String path) + { + if (path == null) + { + throw new ArgumentNullException("path"); + } + VerifyAccess(access); + m_access = access; + m_userPath = path; + if (path.Equals(String.Empty, StringComparison.OrdinalIgnoreCase)) + { + m_canonicalizedPath = String.Empty; + } + else + { + VerifyPath(path); + m_canonicalizedPath = System.IO.Path.GetFullPathInternal(path); + } + } + + // slight perf savings for trusted internal callers + internal FileSecurityState(FileSecurityStateAccess access, String path, String canonicalizedPath) + { + VerifyAccess(access); + VerifyPath(path); + VerifyPath(canonicalizedPath); + + m_access = access; + m_userPath = path; + m_canonicalizedPath = canonicalizedPath; + } + + internal FileSecurityStateAccess Access + { + get + { + return m_access; + } + } + + public String Path { + [System.Runtime.CompilerServices.FriendAccessAllowed] + get + { + return m_canonicalizedPath; + } + } + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #endif + public override void EnsureState() + { + // this is the case for empty string machine name, etc + if (String.Empty.Equals(m_canonicalizedPath)) + return; + + if (!IsStateAvailable()) + { + throw new SecurityException(Environment.GetResourceString("FileSecurityState_OperationNotPermitted", (m_userPath == null) ? String.Empty : m_userPath)); + } + } + + internal static FileSecurityStateAccess ToFileSecurityState(FileIOPermissionAccess access) + { + Contract.Requires((access & ~FileIOPermissionAccess.AllAccess) == 0); + return (FileSecurityStateAccess)access; // flags are identical; just cast + } + + private static void VerifyAccess(FileSecurityStateAccess access) + { + if ((access & ~FileSecurityStateAccess.AllAccess) != 0) + throw new ArgumentOutOfRangeException("access", Environment.GetResourceString("Arg_EnumIllegalVal")); + } + + private static void VerifyPath(String path) + { + if (path != null) + { + path = path.Trim(); + +#if !PLATFORM_UNIX + if (!PathInternal.IsDevice(path) && PathInternal.HasInvalidVolumeSeparator(path)) + throw new ArgumentException(Environment.GetResourceString("Argument_PathFormatNotSupported")); +#endif + + System.IO.Path.CheckInvalidPathChars(path, checkAdditional: true); + } + } + } +} diff --git a/src/mscorlib/src/System/IO/FileSecurityStateAccess.cs b/src/mscorlib/src/System/IO/FileSecurityStateAccess.cs new file mode 100644 index 0000000000..b6378c6142 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileSecurityStateAccess.cs @@ -0,0 +1,32 @@ +// 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. + +/*============================================================ +** +** Enum: FileSecurityStateAccess +** +** +** +** +** Purpose: FileSecurityState enum +** +** +===========================================================*/ + +using System; + +namespace System.IO +{ + [Flags] + internal enum FileSecurityStateAccess + { + NoAccess = 0, + Read = 1, + Write = 2, + Append = 4, + PathDiscovery = 8, + AllAccess = 15 + } +} + diff --git a/src/mscorlib/src/System/IO/FileShare.cs b/src/mscorlib/src/System/IO/FileShare.cs new file mode 100644 index 0000000000..ad76d5da09 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileShare.cs @@ -0,0 +1,60 @@ +// 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. + +/*============================================================ +** +** Enum: FileShare +** +** +** +** +** Purpose: Enum describing how to share files with other +** processes - ie, whether two processes can simultaneously +** read from the same file. +** +** +===========================================================*/ + +using System; + +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 + // +[Serializable] +[Flags] +[System.Runtime.InteropServices.ComVisible(true)] + 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/src/mscorlib/src/System/IO/FileStream.cs b/src/mscorlib/src/System/IO/FileStream.cs new file mode 100644 index 0000000000..deef30c480 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileStream.cs @@ -0,0 +1,2695 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Exposes a Stream around a file, with full +** synchronous and asychronous support, and buffering. +** +** +===========================================================*/ +using System; +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; +using System.Security; +#if FEATURE_MACL +using System.Security.AccessControl; +#endif +using System.Security.Permissions; +using System.Threading; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +#if FEATURE_REMOTING +using System.Runtime.Remoting.Messaging; +#endif +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; +using System.Diagnostics.Tracing; + +/* + * FileStream supports different modes of accessing the disk - async mode + * and sync mode. They are two completely different codepaths in the + * sync & async methods (ie, Read/Write vs. BeginRead/BeginWrite). File + * handles in NT can be opened in only sync or overlapped (async) mode, + * and we have to deal with this pain. Stream has implementations of + * the sync methods in terms of the async ones, so we'll + * call through to our base class to get those methods when necessary. + * + * Also buffering is added into FileStream as well. Folded in the + * code from BufferedStream, so all the comments about it being mostly + * aggressive (and the possible perf improvement) apply to FileStream as + * well. Also added some buffering to the async code paths. + * + * Class Invariants: + * The class has one buffer, shared for reading & writing. It can only be + * used for one or the other at any point in time - not both. The following + * should be true: + * 0 <= _readPos <= _readLen < _bufferSize + * 0 <= _writePos < _bufferSize + * _readPos == _readLen && _readPos > 0 implies the read buffer is valid, + * but we're at the end of the buffer. + * _readPos == _readLen == 0 means the read buffer contains garbage. + * Either _writePos can be greater than 0, or _readLen & _readPos can be + * greater than zero, but neither can be greater than zero at the same time. + * + */ + +namespace System.IO { + + // This is an internal object implementing IAsyncResult with fields + // for all of the relevant data necessary to complete the IO operation. + // This is used by AsyncFSCallback and all of the async methods. + // We should probably make this a nested type of FileStream. But + // I don't know how to define a nested class in mscorlib.h + + unsafe internal sealed class FileStreamAsyncResult : IAsyncResult + { + // README: + // If you modify the order of these fields, make sure to update + // the native VM definition of this class as well!!! + // User code callback + private AsyncCallback _userCallback; + private Object _userStateObject; + private ManualResetEvent _waitHandle; + [System.Security.SecurityCritical] + private SafeFileHandle _handle; // For cancellation support. + + [SecurityCritical] + private NativeOverlapped* _overlapped; + internal NativeOverlapped* OverLapped { [SecurityCritical]get { return _overlapped; } } + internal bool IsAsync { [SecuritySafeCritical]get { return _overlapped != null; } } + + + internal int _EndXxxCalled; // Whether we've called EndXxx already. + private int _numBytes; // number of bytes read OR written + internal int NumBytes { get { return _numBytes; } } + + private int _errorCode; + internal int ErrorCode { get { return _errorCode; } } + + private int _numBufferedBytes; + internal int NumBufferedBytes { get { return _numBufferedBytes; } } + + internal int NumBytesRead { get { return _numBytes + _numBufferedBytes; } } + + private bool _isWrite; // Whether this is a read or a write + internal bool IsWrite { get { return _isWrite; } } + + private bool _isComplete; // Value for IsCompleted property + private bool _completedSynchronously; // Which thread called callback + + // The NativeOverlapped struct keeps a GCHandle to this IAsyncResult object. + // So if the user doesn't call EndRead/EndWrite, a finalizer won't help because + // it'll never get called. + + // Overlapped class will take care of the async IO operations in progress + // when an appdomain unload occurs. + + [System.Security.SecurityCritical] // auto-generated + private unsafe static IOCompletionCallback s_IOCallback; + + [SecuritySafeCritical] + internal FileStreamAsyncResult( + int numBufferedBytes, + byte[] bytes, + SafeFileHandle handle, + AsyncCallback userCallback, + Object userStateObject, + bool isWrite) + { + _userCallback = userCallback; + _userStateObject = userStateObject; + _isWrite = isWrite; + _numBufferedBytes = numBufferedBytes; + _handle = handle; + + // For Synchronous IO, I could go with either a callback and using + // the managed Monitor class, or I could create a handle and wait on it. + ManualResetEvent waitHandle = new ManualResetEvent(false); + _waitHandle = waitHandle; + + // Create a managed overlapped class + // We will set the file offsets later + Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, this); + + // Pack the Overlapped class, and store it in the async result + if (userCallback != null) + { + var ioCallback = s_IOCallback; // cached static delegate; delay initialized due to it being SecurityCritical + if (ioCallback == null) s_IOCallback = ioCallback = new IOCompletionCallback(AsyncFSCallback); + _overlapped = overlapped.Pack(ioCallback, bytes); + } + else + { + _overlapped = overlapped.UnsafePack(null, bytes); + } + + Contract.Assert(_overlapped != null, "Did Overlapped.Pack or Overlapped.UnsafePack just return a null?"); + } + + internal static FileStreamAsyncResult CreateBufferedReadResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite) + { + FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytes, userCallback, userStateObject, isWrite); + asyncResult.CallUserCallback(); + return asyncResult; + } + + // This creates a synchronous Async Result. We should consider making this a separate class and maybe merge it with + // System.IO.Stream.SynchronousAsyncResult + private FileStreamAsyncResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite) + { + _userCallback = userCallback; + _userStateObject = userStateObject; + _isWrite = isWrite; + _numBufferedBytes = numBufferedBytes; + } + + public Object AsyncState + { + get { return _userStateObject; } + } + + public bool IsCompleted + { + get { return _isComplete; } + } + + public WaitHandle AsyncWaitHandle + { + [System.Security.SecuritySafeCritical] // auto-generated + get { + // Consider uncommenting this someday soon - the EventHandle + // in the Overlapped struct is really useless half of the + // time today since the OS doesn't signal it. If users call + // EndXxx after the OS call happened to complete, there's no + // reason to create a synchronization primitive here. Fixing + // this will save us some perf, assuming we can correctly + // initialize the ManualResetEvent. + if (_waitHandle == null) { + ManualResetEvent mre = new ManualResetEvent(false); + if (_overlapped != null && _overlapped->EventHandle != IntPtr.Zero) { + mre.SafeWaitHandle = new SafeWaitHandle(_overlapped->EventHandle, true); + } + + // make sure only one thread sets _waitHandle + if (Interlocked.CompareExchange<ManualResetEvent>(ref _waitHandle, mre, null) == null) { + if (_isComplete) + _waitHandle.Set(); + } + else { + // There's a slight but acceptable race condition if we weren't + // the thread that set _waitHandle and this code path + // returns before the code in the if statement + // executes (on the other thread). However, the + // caller is waiting for the wait handle to be set, + // which will still happen. + mre.Close(); + } + } + return _waitHandle; + } + } + + // Returns true iff the user callback was called by the thread that + // called BeginRead or BeginWrite. If we use an async delegate or + // threadpool thread internally, this will be false. This is used + // by code to determine whether a successive call to BeginRead needs + // to be done on their main thread or in their callback to avoid a + // stack overflow on many reads or writes. + public bool CompletedSynchronously + { + get { return _completedSynchronously; } + } + + private void CallUserCallbackWorker() + { + _isComplete = true; + + // ensure _isComplete is set before reading _waitHandle + Thread.MemoryBarrier(); + if (_waitHandle != null) + _waitHandle.Set(); + + _userCallback(this); + } + + internal void CallUserCallback() + { + // Convenience method for me, since I have to do this in a number + // of places in the buffering code for fake IAsyncResults. + // AsyncFSCallback intentionally does not use this method. + + if (_userCallback != null) { + // Call user's callback on a threadpool thread. + // Set completedSynchronously to false, since it's on another + // thread, not the main thread. + _completedSynchronously = false; + ThreadPool.QueueUserWorkItem(state => ((FileStreamAsyncResult)state).CallUserCallbackWorker(), this); + } + else { + _isComplete = true; + + // ensure _isComplete is set before reading _waitHandle + Thread.MemoryBarrier(); + if (_waitHandle != null) + _waitHandle.Set(); + } + } + + [SecurityCritical] + internal void ReleaseNativeResource() + { + // Free memory & GC handles. + if (this._overlapped != null) + Overlapped.Free(_overlapped); + } + + internal void Wait() + { + if (_waitHandle != null) + { + // We must block to ensure that AsyncFSCallback has completed, + // and we should close the WaitHandle in here. AsyncFSCallback + // and the hand-ported imitation version in COMThreadPool.cpp + // are the only places that set this event. + try + { + _waitHandle.WaitOne(); + Contract.Assert(_isComplete == true, "FileStreamAsyncResult::Wait - AsyncFSCallback didn't set _isComplete to true!"); + } + finally + { + _waitHandle.Close(); + } + } + } + + // When doing IO asynchronously (ie, _isAsync==true), this callback is + // called by a free thread in the threadpool when the IO operation + // completes. + [System.Security.SecurityCritical] // auto-generated + unsafe private static void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) + { + BCLDebug.Log(String.Format("AsyncFSCallback called. errorCode: " + errorCode + " numBytes: " + numBytes)); + + // Unpack overlapped + Overlapped overlapped = Overlapped.Unpack(pOverlapped); + // Free the overlapped struct in EndRead/EndWrite. + + // Extract async result from overlapped + FileStreamAsyncResult asyncResult = + (FileStreamAsyncResult)overlapped.AsyncResult; + asyncResult._numBytes = (int)numBytes; + + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferReceive((long)(asyncResult.OverLapped), 2, string.Empty); + + // Handle reading from & writing to closed pipes. While I'm not sure + // this is entirely necessary anymore, maybe it's possible for + // an async read on a pipe to be issued and then the pipe is closed, + // returning this error. This may very well be necessary. + if (errorCode == FileStream.ERROR_BROKEN_PIPE || errorCode == FileStream.ERROR_NO_DATA) + errorCode = 0; + + asyncResult._errorCode = (int)errorCode; + + // Call the user-provided callback. It can and often should + // call EndRead or EndWrite. There's no reason to use an async + // delegate here - we're already on a threadpool thread. + // IAsyncResult's completedSynchronously property must return + // false here, saying the user callback was called on another thread. + asyncResult._completedSynchronously = false; + asyncResult._isComplete = true; + + // ensure _isComplete is set before reading _waitHandle + Thread.MemoryBarrier(); + + // The OS does not signal this event. We must do it ourselves. + ManualResetEvent wh = asyncResult._waitHandle; + if (wh != null) + { + Contract.Assert(!wh.SafeWaitHandle.IsClosed, "ManualResetEvent already closed!"); + bool r = wh.Set(); + Contract.Assert(r, "ManualResetEvent::Set failed!"); + if (!r) __Error.WinIOError(); + } + + AsyncCallback userCallback = asyncResult._userCallback; + if (userCallback != null) + userCallback(asyncResult); + } + + [SecuritySafeCritical] + [HostProtection(ExternalThreading = true)] + internal void Cancel() + { + Contract.Assert(_handle != null, "_handle should not be null."); + Contract.Assert(_overlapped != null, "Cancel should only be called on true asynchronous FileStreamAsyncResult, i.e. _overlapped is not null"); + + if (IsCompleted) + return; + + if (_handle.IsInvalid) + return; + + bool r = Win32Native.CancelIoEx(_handle, _overlapped); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + + // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel. + // This probably means that the IO operation has completed. + if (errorCode != Win32Native.ERROR_NOT_FOUND) + __Error.WinIOError(errorCode, String.Empty); + } + } + } + + [ComVisible(true)] + public class FileStream : Stream + { + internal const int DefaultBufferSize = 4096; + + private byte[] _buffer; // Shared read/write buffer. Alloc on first use. + private String _fileName; // Fully qualified file name. + private bool _isAsync; // Whether we opened the handle for overlapped IO + private bool _canRead; + private bool _canWrite; + private bool _canSeek; + private bool _exposedHandle; // Could other code be using this handle? + private bool _isPipe; // Whether to disable async buffering code. + private int _readPos; // Read pointer within shared buffer. + private int _readLen; // Number of bytes read in buffer from file. + private int _writePos; // Write pointer within shared buffer. + private int _bufferSize; // Length of internal buffer, if it's allocated. + [System.Security.SecurityCritical] // auto-generated + private SafeFileHandle _handle; + private long _pos; // Cache current location in the file. + private long _appendStart;// When appending, prevent overwriting file. + private static AsyncCallback s_endReadTask; + private static AsyncCallback s_endWriteTask; + private static Action<object> s_cancelReadHandler; + private static Action<object> s_cancelWriteHandler; + + //This exists only to support IsolatedStorageFileStream. + //Any changes to FileStream must include the corresponding changes in IsolatedStorage. + internal FileStream() { + } +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode) + : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { + } + + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode, FileAccess access) + : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { + } + + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode, FileAccess access, FileShare share) + : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { + } + + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize) + : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) + { + } + +#else // FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode) + : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access) + : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access, FileShare share) + : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize) + : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false) + { + } +#endif // FEATURE_CORECLR + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + : this(path, mode, access, share, bufferSize, options, Path.GetFileName(path), false) + { + } + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) + : this(path, mode, access, share, bufferSize, (useAsync ? FileOptions.Asynchronous : FileOptions.None), Path.GetFileName(path), false) + { + } + +#if FEATURE_MACL + // This constructor is done differently to avoid loading a few more + // classes, and more importantly, to build correctly on Rotor. + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options, FileSecurity fileSecurity) + { + Object pinningHandle; + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share, fileSecurity, out pinningHandle); + try { + Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false); + } + finally { + if (pinningHandle != null) { + GCHandle pinHandle = (GCHandle) pinningHandle; + pinHandle.Free(); + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false); + } +#endif + + [System.Security.SecurityCritical] // auto-generated + internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, false, false); + } + + [System.Security.SecurityCritical] + internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, false); + } + + [System.Security.SecurityCritical] + internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, checkHost); + } + + // AccessControl namespace is not defined in Rotor + [System.Security.SecuritySafeCritical] + private void Init(String path, FileMode mode, FileAccess access, int rights, bool useRights, FileShare share, int bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost) + { + if (path == null) + throw new ArgumentNullException("path", Environment.GetResourceString("ArgumentNull_Path")); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + +#if FEATURE_MACL + FileSystemRights fileSystemRights = (FileSystemRights)rights; +#endif + // msgPath must be safe to hand back to untrusted code. + + _fileName = msgPath; // To handle odd cases of finalizing partially constructed objects. + _exposedHandle = false; + + // don't include inheritable in our bounds check for share + FileShare tempshare = share & ~FileShare.Inheritable; + String badArg = null; + + if (mode < FileMode.CreateNew || mode > FileMode.Append) + badArg = "mode"; + else if (!useRights && (access < FileAccess.Read || access > FileAccess.ReadWrite)) + badArg = "access"; +#if FEATURE_MACL + else if (useRights && (fileSystemRights < FileSystemRights.ReadData || fileSystemRights > FileSystemRights.FullControl)) + badArg = "rights"; +#endif + else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete)) + badArg = "share"; + + if (badArg != null) + throw new ArgumentOutOfRangeException(badArg, Environment.GetResourceString("ArgumentOutOfRange_Enum")); + + // NOTE: any change to FileOptions enum needs to be matched here in the error validation + if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0) + throw new ArgumentOutOfRangeException("options", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + + // Write access validation +#if FEATURE_MACL + if ((!useRights && (access & FileAccess.Write) == 0) + || (useRights && (fileSystemRights & FileSystemRights.Write) == 0)) +#else + if (!useRights && (access & FileAccess.Write) == 0) +#endif //FEATURE_MACL + { + if (mode==FileMode.Truncate || mode==FileMode.CreateNew || mode==FileMode.Create || mode==FileMode.Append) { + // No write access + if (!useRights) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&AccessCombo", mode, access)); +#if FEATURE_MACL + else + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&RightsCombo", mode, fileSystemRights)); +#endif //FEATURE_MACL + } + } + +#if FEATURE_MACL + // FileMode.Truncate only works with GENERIC_WRITE (FileAccess.Write), source:MSDN + // For backcomp use FileAccess.Write when FileSystemRights.Write is specified + if (useRights && (mode == FileMode.Truncate)) { + if (fileSystemRights == FileSystemRights.Write) { + useRights = false; + access = FileAccess.Write; + } + else { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileModeTruncate&RightsCombo", mode, fileSystemRights)); + } + } +#endif + + int fAccess; + if (!useRights) { + fAccess = access == FileAccess.Read? GENERIC_READ: + access == FileAccess.Write? GENERIC_WRITE: + GENERIC_READ | GENERIC_WRITE; + } + else { + fAccess = rights; + } + + // Get absolute path - Security needs this to prevent something + // like trying to create a file in c:\tmp with the name + // "..\WinNT\System32\ntoskrnl.exe". Store it for user convenience. + int maxPath = useLongPath ? Path.MaxLongPath : Path.MaxPath; + String filePath = Path.NormalizePath(path, true, maxPath); + + _fileName = filePath; + + // Prevent access to your disk drives as raw block devices. + if (filePath.StartsWith("\\\\.\\", StringComparison.Ordinal)) + throw new ArgumentException(Environment.GetResourceString("Arg_DevicesNotSupported")); + + // In 4.0, we always construct a FileIOPermission object below. + // If filePath contained a ':', we would throw a NotSupportedException in + // System.Security.Util.StringExpressionSet.CanonicalizePath. + // If filePath contained other illegal characters, we would throw an ArgumentException in + // FileIOPermission.CheckIllegalCharacters. + // In 4.5 we on longer construct the FileIOPermission object in full trust. + // To preserve the 4.0 behavior we do an explicit check for ':' here and also call Path.CheckInvalidPathChars. + // Note that we need to call CheckInvalidPathChars before checking for ':' because that is what FileIOPermission does. + + Path.CheckInvalidPathChars(filePath, true); + +#if !PLATFORM_UNIX + if (filePath.IndexOf( ':', 2 ) != -1) + throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) ); +#endif // !PLATFORM_UNIX + + bool read = false; + +#if FEATURE_MACL + if ((!useRights && (access & FileAccess.Read) != 0) || (useRights && (fileSystemRights & FileSystemRights.ReadAndExecute) != 0)) +#else + if (!useRights && (access & FileAccess.Read) != 0) +#endif //FEATURE_MACL + { + if (mode == FileMode.Append) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidAppendMode")); + else + read = true; + } + + // All demands in full trust domains are no-ops, so skip +#if FEATURE_CAS_POLICY + if (!CodeAccessSecurityEngine.QuickCheckForAllDemands()) +#endif // FEATURE_CAS_POLICY + { + // Build up security permissions required, as well as validate we + // have a sensible set of parameters. IE, creating a brand new file + // for reading doesn't make much sense. + FileIOPermissionAccess secAccess = FileIOPermissionAccess.NoAccess; + + if (read) + { + Contract.Assert(mode != FileMode.Append); + secAccess = secAccess | FileIOPermissionAccess.Read; + } + + // I can't think of any combos of FileMode we should disallow if we + // don't have read access. Writing would pretty much always be valid + // in those cases. + + // For any FileSystemRights other than ReadAndExecute, demand Write permission + // This is probably bit overkill for TakeOwnership etc but we don't have any + // matching FileIOPermissionAccess to demand. It is better that we ask for Write permission. + +#if FEATURE_MACL + // FileMode.OpenOrCreate & FileSystemRights.Synchronize can create 0-byte file; demand write + if ((!useRights && (access & FileAccess.Write) != 0) + || (useRights && (fileSystemRights & (FileSystemRights.Write | FileSystemRights.Delete + | FileSystemRights.DeleteSubdirectoriesAndFiles + | FileSystemRights.ChangePermissions + | FileSystemRights.TakeOwnership)) != 0) + || (useRights && ((fileSystemRights & FileSystemRights.Synchronize) != 0) + && mode==FileMode.OpenOrCreate) + ) +#else + if (!useRights && (access & FileAccess.Write) != 0) +#endif //FEATURE_MACL + { + if (mode==FileMode.Append) + secAccess = secAccess | FileIOPermissionAccess.Append; + else + secAccess = secAccess | FileIOPermissionAccess.Write; + } + +#if FEATURE_MACL + bool specifiedAcl; + unsafe { + specifiedAcl = secAttrs != null && secAttrs.pSecurityDescriptor != null; + } + + AccessControlActions control = specifiedAcl ? AccessControlActions.Change : AccessControlActions.None; + new FileIOPermission(secAccess, control, new String[] { filePath }, false, false).Demand(); +#else +#if FEATURE_CORECLR + if (checkHost) { + FileSecurityState state = new FileSecurityState(FileSecurityState.ToFileSecurityState(secAccess), path, filePath); + state.EnsureState(); + } +#else + new FileIOPermission(secAccess, new String[] { filePath }, false, false).Demand(); +#endif // FEATURE_CORECLR +#endif + } + + // Our Inheritable bit was stolen from Windows, but should be set in + // the security attributes class. Don't leave this bit set. + share &= ~FileShare.Inheritable; + + bool seekToEnd = (mode==FileMode.Append); + // Must use a valid Win32 constant here... + if (mode == FileMode.Append) + mode = FileMode.OpenOrCreate; + + // WRT async IO, do the right thing for whatever platform we're on. + // This way, someone can easily write code that opens a file + // asynchronously no matter what their platform is. + if ((options & FileOptions.Asynchronous) != 0) + _isAsync = true; + else + options &= ~FileOptions.Asynchronous; + + int flagsAndAttributes = (int) options; + +#if !PLATFORM_UNIX + // For mitigating local elevation of privilege attack through named pipes + // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the + // named pipe server can't impersonate a high privileged client security context + flagsAndAttributes |= (Win32Native.SECURITY_SQOS_PRESENT | Win32Native.SECURITY_ANONYMOUS); +#endif + + // Don't pop up a dialog for reading from an emtpy floppy drive + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + String tempPath = filePath; + if (useLongPath) + tempPath = Path.AddLongPathPrefix(tempPath); + _handle = Win32Native.SafeCreateFile(tempPath, fAccess, share, secAttrs, mode, flagsAndAttributes, IntPtr.Zero); + + if (_handle.IsInvalid) { + // Return a meaningful exception, using the RELATIVE path to + // the file to avoid returning extra information to the caller + // unless they have path discovery permission, in which case + // the full path is fine & useful. + + // NT5 oddity - when trying to open "C:\" as a FileStream, + // we usually get ERROR_PATH_NOT_FOUND from the OS. We should + // probably be consistent w/ every other directory. + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode==__Error.ERROR_PATH_NOT_FOUND && filePath.Equals(Directory.InternalGetDirectoryRoot(filePath))) + errorCode = __Error.ERROR_ACCESS_DENIED; + + // We need to give an exception, and preferably it would include + // the fully qualified path name. Do security check here. If + // we fail, give back the msgPath, which should not reveal much. + // While this logic is largely duplicated in + // __Error.WinIOError, we need this for + // IsolatedStorageFileStream. + bool canGiveFullPath = false; + + if (!bFromProxy) + { + try { +#if !FEATURE_CORECLR + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false ).Demand(); +#endif + canGiveFullPath = true; + } + catch(SecurityException) {} + } + + if (canGiveFullPath) + __Error.WinIOError(errorCode, _fileName); + else + __Error.WinIOError(errorCode, msgPath); + } + } + finally { + Win32Native.SetErrorMode(oldMode); + } + + // Disallow access to all non-file devices from the FileStream + // constructors that take a String. Everyone else can call + // CreateFile themselves then use the constructor that takes an + // IntPtr. Disallows "con:", "com1:", "lpt1:", etc. + int fileType = Win32Native.GetFileType(_handle); + if (fileType != Win32Native.FILE_TYPE_DISK) { + _handle.Close(); + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FileStreamOnNonFiles")); + } + + // This is necessary for async IO using IO Completion ports via our + // managed Threadpool API's. This (theoretically) calls the OS's + // BindIoCompletionCallback method, and passes in a stub for the + // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped + // struct for this request and gets a delegate to a managed callback + // from there, which it then calls on a threadpool thread. (We allocate + // our native OVERLAPPED structs 2 pointers too large and store EE state + // & GC handles there, one to an IAsyncResult, the other to a delegate.) + if (_isAsync) { + bool b = false; + // BindHandle requires UnmanagedCode permission +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); +#pragma warning restore 618 + try { + b = ThreadPool.BindHandle(_handle); + } + finally { + CodeAccessPermission.RevertAssert(); + if (!b) { + // We should close the handle so that the handle is not open until SafeFileHandle GC + Contract.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?"); + _handle.Close(); + } + } + if (!b) + throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed")); + } + + if (!useRights) { + _canRead = (access & FileAccess.Read) != 0; + _canWrite = (access & FileAccess.Write) != 0; + } +#if FEATURE_MACL + else { + _canRead = (fileSystemRights & FileSystemRights.ReadData) != 0; + _canWrite = ((fileSystemRights & FileSystemRights.WriteData) != 0) + || ((fileSystemRights & FileSystemRights.AppendData) != 0); + } +#endif //FEATURE_MACL + + _canSeek = true; + _isPipe = false; + _pos = 0; + _bufferSize = bufferSize; + _readPos = 0; + _readLen = 0; + _writePos = 0; + + // For Append mode... + if (seekToEnd) { + _appendStart = SeekCore(0, SeekOrigin.End); + } + else { + _appendStart = -1; + } + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access) + : this(handle, access, true, DefaultBufferSize, false) { + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle) + : this(handle, access, ownsHandle, DefaultBufferSize, false) { + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize) + : this(handle, access, ownsHandle, bufferSize, false) { + } + + // We explicitly do a Demand, not a LinkDemand here. + [System.Security.SecuritySafeCritical] // auto-generated + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#pragma warning restore 618 + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync) + : this(new SafeFileHandle(handle, ownsHandle), access, bufferSize, isAsync) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(SafeFileHandle handle, FileAccess access) + : this(handle, access, DefaultBufferSize, false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) + : this(handle, access, bufferSize, false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#pragma warning restore 618 + public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) { + // To ensure we don't leak a handle, put it in a SafeFileHandle first + if (handle.IsInvalid) + throw new ArgumentException(Environment.GetResourceString("Arg_InvalidHandle"), "handle"); + Contract.EndContractBlock(); + + _handle = handle; + _exposedHandle = true; + + // Now validate arguments. + if (access < FileAccess.Read || access > FileAccess.ReadWrite) + throw new ArgumentOutOfRangeException("access", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + + int handleType = Win32Native.GetFileType(_handle); + Contract.Assert(handleType == Win32Native.FILE_TYPE_DISK || handleType == Win32Native.FILE_TYPE_PIPE || handleType == Win32Native.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!"); + _isAsync = isAsync; + _canRead = 0 != (access & FileAccess.Read); + _canWrite = 0 != (access & FileAccess.Write); + _canSeek = handleType == Win32Native.FILE_TYPE_DISK; + _bufferSize = bufferSize; + _readPos = 0; + _readLen = 0; + _writePos = 0; + _fileName = null; + _isPipe = handleType == Win32Native.FILE_TYPE_PIPE; + + // This is necessary for async IO using IO Completion ports via our + // managed Threadpool API's. This calls the OS's + // BindIoCompletionCallback method, and passes in a stub for the + // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped + // struct for this request and gets a delegate to a managed callback + // from there, which it then calls on a threadpool thread. (We allocate + // our native OVERLAPPED structs 2 pointers too large and store EE + // state & a handle to a delegate there.) +#if !FEATURE_CORECLR + if (_isAsync) { + bool b = false; + try { + b = ThreadPool.BindHandle(_handle); + } + catch (ApplicationException) { + // If you passed in a synchronous handle and told us to use + // it asynchronously, throw here. + throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotAsync")); + } + if (!b) { + throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed")); + } + } + else { +#endif // FEATURE_CORECLR + if (handleType != Win32Native.FILE_TYPE_PIPE) + VerifyHandleIsSync(); +#if !FEATURE_CORECLR + } +#endif // FEATURE_CORECLR + + if (_canSeek) + SeekCore(0, SeekOrigin.Current); + else + _pos = 0; + } + + [System.Security.SecuritySafeCritical] // auto-generated + private static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = null; + if ((share & FileShare.Inheritable) != 0) { + secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); + + secAttrs.bInheritHandle = 1; + } + return secAttrs; + } + +#if FEATURE_MACL + // If pinningHandle is not null, caller must free it AFTER the call to + // CreateFile has returned. + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share, FileSecurity fileSecurity, out Object pinningHandle) + { + pinningHandle = null; + Win32Native.SECURITY_ATTRIBUTES secAttrs = null; + if ((share & FileShare.Inheritable) != 0 || fileSecurity != null) { + secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); + + if ((share & FileShare.Inheritable) != 0) { + secAttrs.bInheritHandle = 1; + } + + // For ACL's, get the security descriptor from the FileSecurity. + if (fileSecurity != null) { + byte[] sd = fileSecurity.GetSecurityDescriptorBinaryForm(); + pinningHandle = GCHandle.Alloc(sd, GCHandleType.Pinned); + fixed(byte* pSecDescriptor = sd) + secAttrs.pSecurityDescriptor = pSecDescriptor; + } + } + return secAttrs; + } +#endif + + // Verifies that this handle supports synchronous IO operations (unless you + // didn't open it for either reading or writing). + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe void VerifyHandleIsSync() + { + // Do NOT use this method on pipes. Reading or writing to a pipe may + // cause an app to block incorrectly, introducing a deadlock (depending + // on whether a write will wake up an already-blocked thread or this + // FileStream's thread). + + // Do NOT change this to use a byte[] of length 0, or test test won't + // work. Our ReadFile & WriteFile methods are special cased to return + // for arrays of length 0, since we'd get an IndexOutOfRangeException + // while using C#'s fixed syntax. + byte[] bytes = new byte[1]; + int hr = 0; + int r = 0; + + // If the handle is a pipe, ReadFile will block until there + // has been a write on the other end. We'll just have to deal with it, + // For the read end of a pipe, you can mess up and + // accidentally read synchronously from an async pipe. + if (CanRead) { + r = ReadFileNative(_handle, bytes, 0, 0, null, out hr); + } + else if (CanWrite) { + r = WriteFileNative(_handle, bytes, 0, 0, null, out hr); + } + + if (hr==ERROR_INVALID_PARAMETER) + throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync")); + if (hr == Win32Native.ERROR_INVALID_HANDLE) + __Error.WinIOError(hr, "<OS handle>"); + } + + + public override bool CanRead { + [Pure] + get { return _canRead; } + } + + public override bool CanWrite { + [Pure] + get { return _canWrite; } + } + + public override bool CanSeek { + [Pure] + get { return _canSeek; } + } + + public virtual bool IsAsync { + get { return _isAsync; } + } + + public override long Length { + [System.Security.SecuritySafeCritical] // auto-generated + get { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + int hi = 0, lo = 0; + + lo = Win32Native.GetFileSize(_handle, out hi); + + if (lo==-1) { // Check for either an error or a 4GB - 1 byte file. + int hr = Marshal.GetLastWin32Error(); + if (hr != 0) + __Error.WinIOError(hr, String.Empty); + } + long len = (((long)hi) << 32) | ((uint) lo); + // If we're writing near the end of the file, we must include our + // internal buffer in our Length calculation. Don't flush because + // we use the length of the file in our async write method. + if (_writePos > 0 && _pos + _writePos > len) + len = _writePos + _pos; + return len; + } + } + + public String Name { + [System.Security.SecuritySafeCritical] + get { + if (_fileName == null) + return Environment.GetResourceString("IO_UnknownFileName"); +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, _fileName); + sourceState.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false).Demand(); +#endif + return _fileName; + } + } + + internal String NameInternal { + get { + if (_fileName == null) + return "<UnknownFileName>"; + return _fileName; + } + } + + public override long Position { + [System.Security.SecuritySafeCritical] // auto-generated + get { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + + Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + // Verify that internal position is in sync with the handle + if (_exposedHandle) + VerifyOSHandlePosition(); + + // Compensate for buffer that we read from the handle (_readLen) Vs what the user + // read so far from the internel buffer (_readPos). Of course add any unwrittern + // buffered data + return _pos + (_readPos - _readLen + _writePos); + } + set { + if (value < 0) throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_writePos > 0) FlushWrite(false); + _readPos = 0; + _readLen = 0; + Seek(value, SeekOrigin.Begin); + } + } + +#if FEATURE_MACL + [System.Security.SecuritySafeCritical] // auto-generated + public FileSecurity GetAccessControl() + { + if (_handle.IsClosed) __Error.FileNotOpen(); + return new FileSecurity(_handle, _fileName, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void SetAccessControl(FileSecurity fileSecurity) + { + if (fileSecurity == null) + throw new ArgumentNullException("fileSecurity"); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + fileSecurity.Persist(_handle, _fileName); + } +#endif + + [System.Security.SecuritySafeCritical] // auto-generated + protected override void Dispose(bool disposing) + { + // Nothing will be done differently based on whether we are + // disposing vs. finalizing. This is taking advantage of the + // weak ordering between normal finalizable objects & critical + // finalizable objects, which I included in the SafeHandle + // design for FileStream, which would often "just work" when + // finalized. + try { + if (_handle != null && !_handle.IsClosed) { + // Flush data to disk iff we were writing. After + // thinking about this, we also don't need to flush + // our read position, regardless of whether the handle + // was exposed to the user. They probably would NOT + // want us to do this. + if (_writePos > 0) { + FlushWrite(!disposing); + } + } + } + finally { + if (_handle != null && !_handle.IsClosed) + _handle.Dispose(); + + _canRead = false; + _canWrite = false; + _canSeek = false; + // Don't set the buffer to null, to avoid a NullReferenceException + // when users have a race condition in their code (ie, they call + // Close when calling another method on Stream like Read). + //_buffer = null; + base.Dispose(disposing); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + ~FileStream() + { + if (_handle != null) { + BCLDebug.Correctness(_handle.IsClosed, "You didn't close a FileStream & it got finalized. Name: \""+_fileName+"\""); + Dispose(false); + } + } + + public override void Flush() + { + Flush(false); + } + + [System.Security.SecuritySafeCritical] + public virtual void Flush(Boolean flushToDisk) + { + // This code is duplicated in Dispose + if (_handle.IsClosed) __Error.FileNotOpen(); + + FlushInternalBuffer(); + + if (flushToDisk && CanWrite) + { + FlushOSBuffer(); + } + } + + private void FlushInternalBuffer() + { + if (_writePos > 0) + { + FlushWrite(false); + } + else if (_readPos < _readLen && CanSeek) + { + FlushRead(); + } + } + + [System.Security.SecuritySafeCritical] + private void FlushOSBuffer() + { + if (!Win32Native.FlushFileBuffers(_handle)) + { + __Error.WinIOError(); + } + } + + // Reading is done by blocks from the file, but someone could read + // 1 byte from the buffer then write. At that point, the OS's file + // pointer is out of sync with the stream's position. All write + // functions should call this function to preserve the position in the file. + private void FlushRead() { + Contract.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushRead!"); + if (_readPos - _readLen != 0) { + Contract.Assert(CanSeek, "FileStream will lose buffered read data now."); + SeekCore(_readPos - _readLen, SeekOrigin.Current); + } + _readPos = 0; + _readLen = 0; + } + + // Writes are buffered. Anytime the buffer fills up + // (_writePos + delta > _bufferSize) or the buffer switches to reading + // and there is left over data (_writePos > 0), this function must be called. + private void FlushWrite(bool calledFromFinalizer) { + Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWrite!"); + + if (_isAsync) { + IAsyncResult asyncResult = BeginWriteCore(_buffer, 0, _writePos, null, null); + // With our Whidbey async IO & overlapped support for AD unloads, + // we don't strictly need to block here to release resources + // since that support takes care of the pinning & freeing the + // overlapped struct. We need to do this when called from + // Close so that the handle is closed when Close returns, but + // we do't need to call EndWrite from the finalizer. + // Additionally, if we do call EndWrite, we block forever + // because AD unloads prevent us from running the managed + // callback from the IO completion port. Blocking here when + // called from the finalizer during AD unload is clearly wrong, + // but we can't use any sort of test for whether the AD is + // unloading because if we weren't unloading, an AD unload + // could happen on a separate thread before we call EndWrite. + if (!calledFromFinalizer) + EndWrite(asyncResult); + } + else + WriteCore(_buffer, 0, _writePos); + + _writePos = 0; + } + + + [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public virtual IntPtr Handle { + [System.Security.SecurityCritical] // auto-generated_required +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#endif + get { + Flush(); + // Explicitly dump any buffered data, since the user could move our + // position or write to the file. + _readPos = 0; + _readLen = 0; + _writePos = 0; + _exposedHandle = true; + + return _handle.DangerousGetHandle(); + } + } + + public virtual SafeFileHandle SafeFileHandle { + [System.Security.SecurityCritical] // auto-generated_required +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#endif + get { + Flush(); + // Explicitly dump any buffered data, since the user could move our + // position or write to the file. + _readPos = 0; + _readLen = 0; + _writePos = 0; + _exposedHandle = true; + + return _handle; + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void SetLength(long value) + { + if (value < 0) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + if (!CanWrite) __Error.WriteNotSupported(); + + // Handle buffering updates. + if (_writePos > 0) { + FlushWrite(false); + } + else if (_readPos < _readLen) { + FlushRead(); + } + _readPos = 0; + _readLen = 0; + + if (_appendStart != -1 && value < _appendStart) + throw new IOException(Environment.GetResourceString("IO.IO_SetLengthAppendTruncate")); + SetLengthCore(value); + } + + // We absolutely need this method broken out so that BeginWriteCore can call + // a method without having to go through buffering code that might call + // FlushWrite. + [System.Security.SecuritySafeCritical] // auto-generated + private void SetLengthCore(long value) + { + Contract.Assert(value >= 0, "value >= 0"); + long origPos = _pos; + + if (_exposedHandle) + VerifyOSHandlePosition(); + if (_pos != value) + SeekCore(value, SeekOrigin.Begin); + if (!Win32Native.SetEndOfFile(_handle)) { + int hr = Marshal.GetLastWin32Error(); + if (hr==__Error.ERROR_INVALID_PARAMETER) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_FileLengthTooBig")); + __Error.WinIOError(hr, String.Empty); + } + // Return file pointer to where it was before setting length + if (origPos != value) { + if (origPos < value) + SeekCore(origPos, SeekOrigin.Begin); + else + SeekCore(0, SeekOrigin.End); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override int Read([In, Out] byte[] array, int offset, int count) { + if (array==null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + bool isBlocked = false; + int n = _readLen - _readPos; + // if the read buffer is empty, read into either user's array or our + // buffer, depending on number of bytes user asked for and buffer size. + if (n == 0) { + if (!CanRead) __Error.ReadNotSupported(); + if (_writePos > 0) FlushWrite(false); + if (!CanSeek || (count >= _bufferSize)) { + n = ReadCore(array, offset, count); + // Throw away read buffer. + _readPos = 0; + _readLen = 0; + return n; + } + if (_buffer == null) _buffer = new byte[_bufferSize]; + n = ReadCore(_buffer, 0, _bufferSize); + if (n == 0) return 0; + isBlocked = n < _bufferSize; + _readPos = 0; + _readLen = n; + } + // Now copy min of count or numBytesAvailable (ie, near EOF) to array. + if (n > count) n = count; + Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); + _readPos += n; + + // We may have read less than the number of bytes the user asked + // for, but that is part of the Stream contract. Reading again for + // more data may cause us to block if we're using a device with + // no clear end of file, such as a serial port or pipe. If we + // blocked here & this code was used with redirected pipes for a + // process's standard output, this can lead to deadlocks involving + // two processes. But leave this here for files to avoid what would + // probably be a breaking change. -- + + // If we are reading from a device with no clear EOF like a + // serial port or a pipe, this will cause us to block incorrectly. + if (!_isPipe) { + // If we hit the end of the buffer and didn't have enough bytes, we must + // read some more from the underlying stream. However, if we got + // fewer bytes from the underlying stream than we asked for (ie, we're + // probably blocked), don't ask for more bytes. + if (n < count && !isBlocked) { + Contract.Assert(_readPos == _readLen, "Read buffer should be empty!"); + int moreBytesRead = ReadCore(array, offset + n, count - n); + n += moreBytesRead; + // We've just made our buffer inconsistent with our position + // pointer. We must throw away the read buffer. + _readPos = 0; + _readLen = 0; + } + } + + return n; + } + + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe int ReadCore(byte[] buffer, int offset, int count) { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanRead, "CanRead"); + + Contract.Assert(buffer != null, "buffer != null"); + Contract.Assert(_writePos == 0, "_writePos == 0"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(count >= 0, "count is negative"); + + if (_isAsync) { + IAsyncResult result = BeginReadCore(buffer, offset, count, null, null, 0); + return EndRead(result); + } + + // Make sure we are reading from the right spot + if (_exposedHandle) + VerifyOSHandlePosition(); + + int hr = 0; + int r = ReadFileNative(_handle, buffer, offset, count, null, out hr); + if (r == -1) { + // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. + if (hr == ERROR_BROKEN_PIPE) { + r = 0; + } + else { + if (hr == ERROR_INVALID_PARAMETER) + throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync")); + + __Error.WinIOError(hr, String.Empty); + } + } + Contract.Assert(r >= 0, "FileStream's ReadCore is likely broken."); + _pos += r; + + return r; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override long Seek(long offset, SeekOrigin origin) { + if (origin<SeekOrigin.Begin || origin>SeekOrigin.End) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin")); + Contract.EndContractBlock(); + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + + Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + // If we've got bytes in our buffer to write, write them out. + // If we've read in and consumed some bytes, we'll have to adjust + // our seek positions ONLY IF we're seeking relative to the current + // position in the stream. This simulates doing a seek to the new + // position, then a read for the number of bytes we have in our buffer. + if (_writePos > 0) { + FlushWrite(false); + } + else if (origin == SeekOrigin.Current) { + // Don't call FlushRead here, which would have caused an infinite + // loop. Simply adjust the seek origin. This isn't necessary + // if we're seeking relative to the beginning or end of the stream. + offset -= (_readLen - _readPos); + } + + // Verify that internal position is in sync with the handle + if (_exposedHandle) + VerifyOSHandlePosition(); + + long oldPos = _pos + (_readPos - _readLen); + long pos = SeekCore(offset, origin); + + // Prevent users from overwriting data in a file that was opened in + // append mode. + if (_appendStart != -1 && pos < _appendStart) { + SeekCore(oldPos, SeekOrigin.Begin); + throw new IOException(Environment.GetResourceString("IO.IO_SeekAppendOverwrite")); + } + + // We now must update the read buffer. We can in some cases simply + // update _readPos within the buffer, copy around the buffer so our + // Position property is still correct, and avoid having to do more + // reads from the disk. Otherwise, discard the buffer's contents. + if (_readLen > 0) { + // We can optimize the following condition: + // oldPos - _readPos <= pos < oldPos + _readLen - _readPos + if (oldPos == pos) { + if (_readPos > 0) { + //Console.WriteLine("Seek: seeked for 0, adjusting buffer back by: "+_readPos+" _readLen: "+_readLen); + Buffer.InternalBlockCopy(_buffer, _readPos, _buffer, 0, _readLen - _readPos); + _readLen -= _readPos; + _readPos = 0; + } + // If we still have buffered data, we must update the stream's + // position so our Position property is correct. + if (_readLen > 0) + SeekCore(_readLen, SeekOrigin.Current); + } + else if (oldPos - _readPos < pos && pos < oldPos + _readLen - _readPos) { + int diff = (int)(pos - oldPos); + //Console.WriteLine("Seek: diff was "+diff+", readpos was "+_readPos+" adjusting buffer - shrinking by "+ (_readPos + diff)); + Buffer.InternalBlockCopy(_buffer, _readPos+diff, _buffer, 0, _readLen - (_readPos + diff)); + _readLen -= (_readPos + diff); + _readPos = 0; + if (_readLen > 0) + SeekCore(_readLen, SeekOrigin.Current); + } + else { + // Lose the read buffer. + _readPos = 0; + _readLen = 0; + } + Contract.Assert(_readLen >= 0 && _readPos <= _readLen, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen"); + Contract.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled."); + } + return pos; + } + + // This doesn't do argument checking. Necessary for SetLength, which must + // set the file pointer beyond the end of the file. This will update the + // internal position + [System.Security.SecuritySafeCritical] // auto-generated + private long SeekCore(long offset, SeekOrigin origin) { + Contract.Assert(!_handle.IsClosed && CanSeek, "!_handle.IsClosed && CanSeek"); + Contract.Assert(origin>=SeekOrigin.Begin && origin<=SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End"); + int hr = 0; + long ret = 0; + + ret = Win32Native.SetFilePointer(_handle, offset, origin, out hr); + if (ret == -1) { + // #errorInvalidHandle + // If ERROR_INVALID_HANDLE is returned, it doesn't suffice to set + // the handle as invalid; the handle must also be closed. + // + // Marking the handle as invalid but not closing the handle + // resulted in exceptions during finalization and locked column + // values (due to invalid but unclosed handle) in SQL FileStream + // scenarios. + // + // A more mainstream scenario involves accessing a file on a + // network share. ERROR_INVALID_HANDLE may occur because the network + // connection was dropped and the server closed the handle. However, + // the client side handle is still open and even valid for certain + // operations. + // + // Note that Dispose doesn't throw so we don't need to special case. + // SetHandleAsInvalid only sets _closed field to true (without + // actually closing handle) so we don't need to call that as well. + if (hr == Win32Native.ERROR_INVALID_HANDLE) + _handle.Dispose(); + __Error.WinIOError(hr, String.Empty); + } + + _pos = ret; + return ret; + } + + // Checks the position of the OS's handle equals what we expect it to. + // This will fail if someone else moved the FileStream's handle or if + // we've hit a bug in FileStream's position updating code. + private void VerifyOSHandlePosition() + { + if (!CanSeek) + return; + + // SeekCore will override the current _pos, so save it now + long oldPos = _pos; + long curPos = SeekCore(0, SeekOrigin.Current); + + if (curPos != oldPos) { + // For reads, this is non-fatal but we still could have returned corrupted + // data in some cases. So discard the internal buffer. Potential MDA + _readPos = 0; + _readLen = 0; + if(_writePos > 0) { + // Discard the buffer and let the user know! + _writePos = 0; + throw new IOException(Environment.GetResourceString("IO.IO_FileStreamHandlePosition")); + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void Write(byte[] array, int offset, int count) { + if (array==null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + if (_writePos == 0) + { + // Ensure we can write to the stream, and ready buffer for writing. + if (!CanWrite) __Error.WriteNotSupported(); + if (_readPos < _readLen) FlushRead(); + _readPos = 0; + _readLen = 0; + } + + // If our buffer has data in it, copy data from the user's array into + // the buffer, and if we can fit it all there, return. Otherwise, write + // the buffer to disk and copy any remaining data into our buffer. + // The assumption here is memcpy is cheaper than disk (or net) IO. + // (10 milliseconds to disk vs. ~20-30 microseconds for a 4K memcpy) + // So the extra copying will reduce the total number of writes, in + // non-pathological cases (ie, write 1 byte, then write for the buffer + // size repeatedly) + if (_writePos > 0) { + int numBytes = _bufferSize - _writePos; // space left in buffer + if (numBytes > 0) { + if (numBytes > count) + numBytes = count; + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes); + _writePos += numBytes; + if (count==numBytes) return; + offset += numBytes; + count -= numBytes; + } + // Reset our buffer. We essentially want to call FlushWrite + // without calling Flush on the underlying Stream. + + if (_isAsync) { + IAsyncResult result = BeginWriteCore(_buffer, 0, _writePos, null, null); + EndWrite(result); + } + else + { + WriteCore(_buffer, 0, _writePos); + } + + _writePos = 0; + } + // If the buffer would slow writes down, avoid buffer completely. + if (count >= _bufferSize) { + Contract.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted."); + WriteCore(array, offset, count); + return; + } + else if (count == 0) + return; // Don't allocate a buffer then call memcpy for 0 bytes. + if (_buffer==null) _buffer = new byte[_bufferSize]; + // Copy remaining bytes into buffer, to write at a later date. + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, count); + _writePos = count; + return; + } + + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe void WriteCore(byte[] buffer, int offset, int count) { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanWrite, "CanWrite"); + + Contract.Assert(buffer != null, "buffer != null"); + Contract.Assert(_readPos == _readLen, "_readPos == _readLen"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(count >= 0, "count is negative"); + + if (_isAsync) { + IAsyncResult result = BeginWriteCore(buffer, offset, count, null, null); + EndWrite(result); + return; + } + + // Make sure we are writing to the position that we think we are + if (_exposedHandle) + VerifyOSHandlePosition(); + + int hr = 0; + int r = WriteFileNative(_handle, buffer, offset, count, null, out hr); + if (r == -1) { + // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. + if (hr == ERROR_NO_DATA) { + r = 0; + } + else { + // ERROR_INVALID_PARAMETER may be returned for writes + // where the position is too large (ie, writing at Int64.MaxValue + // on Win9x) OR for synchronous writes to a handle opened + // asynchronously. + if (hr == ERROR_INVALID_PARAMETER) + throw new IOException(Environment.GetResourceString("IO.IO_FileTooLongOrHandleNotSync")); + __Error.WinIOError(hr, String.Empty); + } + } + Contract.Assert(r >= 0, "FileStream's WriteCore is likely broken."); + _pos += r; + return; + } + + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading = true)] + public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + if (array==null) + throw new ArgumentNullException("array"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (numBytes < 0) + throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < numBytes) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + if (!_isAsync) + return base.BeginRead(array, offset, numBytes, userCallback, stateObject); + else + return BeginReadAsync(array, offset, numBytes, userCallback, stateObject); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading = true)] + private FileStreamAsyncResult BeginReadAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + Contract.Assert(_isAsync); + + if (!CanRead) __Error.ReadNotSupported(); + + Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + if (_isPipe) + { + // When redirecting stdout & stderr with the Process class, it's easy to deadlock your + // parent & child processes when doing writes 4K at a time. The + // OS appears to use a 4K buffer internally. If you write to a + // pipe that is full, you will block until someone read from + // that pipe. If you try reading from an empty pipe and + // FileStream's BeginRead blocks waiting for data to fill it's + // internal buffer, you will be blocked. In a case where a child + // process writes to stdout & stderr while a parent process tries + // reading from both, you can easily get into a deadlock here. + // To avoid this deadlock, don't buffer when doing async IO on + // pipes. But don't completely ignore buffered data either. + if (_readPos < _readLen) + { + int n = _readLen - _readPos; + if (n > numBytes) n = numBytes; + Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); + _readPos += n; + + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); + } + else + { + Contract.Assert(_writePos == 0, "FileStream must not have buffered write data here! Pipes should be unidirectional."); + return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0); + } + } + + Contract.Assert(!_isPipe, "Should not be a pipe."); + + // Handle buffering. + if (_writePos > 0) FlushWrite(false); + if (_readPos == _readLen) + { + // I can't see how to handle buffering of async requests when + // filling the buffer asynchronously, without a lot of complexity. + // The problems I see are issuing an async read, we do an async + // read to fill the buffer, then someone issues another read + // (either synchronously or asynchronously) before the first one + // returns. This would involve some sort of complex buffer locking + // that we probably don't want to get into, at least not in V1. + // If we did a sync read to fill the buffer, we could avoid the + // problem, and any async read less than 64K gets turned into a + // synchronous read by NT anyways... -- + + if (numBytes < _bufferSize) + { + if (_buffer == null) _buffer = new byte[_bufferSize]; + IAsyncResult bufferRead = BeginReadCore(_buffer, 0, _bufferSize, null, null, 0); + _readLen = EndRead(bufferRead); + int n = _readLen; + if (n > numBytes) n = numBytes; + Buffer.InternalBlockCopy(_buffer, 0, array, offset, n); + _readPos = n; + + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); + } + else + { + // Here we're making our position pointer inconsistent + // with our read buffer. Throw away the read buffer's contents. + _readPos = 0; + _readLen = 0; + return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0); + } + } + else + { + int n = _readLen - _readPos; + if (n > numBytes) n = numBytes; + Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); + _readPos += n; + + if (n >= numBytes) + { + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); + } + else + { + // For streams with no clear EOF like serial ports or pipes + // we cannot read more data without causing an app to block + // incorrectly. Pipes don't go down this path + // though. This code needs to be fixed. + // Throw away read buffer. + _readPos = 0; + _readLen = 0; + return BeginReadCore(array, offset + n, numBytes - n, userCallback, stateObject, n); + } + // WARNING: all state on asyncResult objects must be set before + // we call ReadFile in BeginReadCore, since the OS can run our + // callback & the user's callback before ReadFile returns. + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + unsafe private FileStreamAsyncResult BeginReadCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject, int numBufferedBytesRead) + { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanRead, "CanRead"); + Contract.Assert(bytes != null, "bytes != null"); + Contract.Assert(_writePos == 0, "_writePos == 0"); + Contract.Assert(_isAsync, "BeginReadCore doesn't work on synchronous file streams!"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(numBytes >= 0, "numBytes is negative"); + + // Create and store async stream class library specific data in the async result + + // Must pass in _numBufferedBytes here to ensure all the state on the IAsyncResult + // object is set before we call ReadFile, which gives the OS an + // opportunity to run our callback (including the user callback & + // the call to EndRead) before ReadFile has returned. + FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytesRead, bytes, _handle, userCallback, stateObject, false); + NativeOverlapped* intOverlapped = asyncResult.OverLapped; + + // Calculate position in the file we should be at after the read is done + if (CanSeek) { + long len = Length; + + // Make sure we are reading from the position that we think we are + if (_exposedHandle) + VerifyOSHandlePosition(); + + if (_pos + numBytes > len) { + if (_pos <= len) + numBytes = (int) (len - _pos); + else + numBytes = 0; + } + + // Now set the position to read from in the NativeOverlapped struct + // For pipes, we should leave the offset fields set to 0. + intOverlapped->OffsetLow = unchecked((int)_pos); + intOverlapped->OffsetHigh = (int)(_pos>>32); + + // When using overlapped IO, the OS is not supposed to + // touch the file pointer location at all. We will adjust it + // ourselves. This isn't threadsafe. + + // WriteFile should not update the file pointer when writing + // in overlapped mode, according to MSDN. But it does update + // the file pointer when writing to a UNC path! + // So changed the code below to seek to an absolute + // location, not a relative one. ReadFile seems consistent though. + SeekCore(numBytes, SeekOrigin.Current); + } + + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false); + + // queue an async ReadFile operation and pass in a packed overlapped + int hr = 0; + int r = ReadFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); + // ReadFile, the OS version, will return 0 on failure. But + // my ReadFileNative wrapper returns -1. My wrapper will return + // the following: + // On error, r==-1. + // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING + // on async requests that completed sequentially, r==0 + // You will NEVER RELIABLY be able to get the number of bytes + // read back from this call when using overlapped structures! You must + // not pass in a non-null lpNumBytesRead to ReadFile when using + // overlapped structures! This is by design NT behavior. + if (r==-1 && numBytes!=-1) { + + // For pipes, when they hit EOF, they will come here. + if (hr == ERROR_BROKEN_PIPE) { + // Not an error, but EOF. AsyncFSCallback will NOT be + // called. Call the user callback here. + + // We clear the overlapped status bit for this special case. + // Failure to do so looks like we are freeing a pending overlapped later. + intOverlapped->InternalLow = IntPtr.Zero; + asyncResult.CallUserCallback(); + // EndRead will free the Overlapped struct correctly. + } + else if (hr != ERROR_IO_PENDING) { + if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. + SeekCore(0, SeekOrigin.Current); + + if (hr == ERROR_HANDLE_EOF) + __Error.EndOfFile(); + else + __Error.WinIOError(hr, String.Empty); + } + } + else { + // Due to a workaround for a race condition in NT's ReadFile & + // WriteFile routines, we will always be returning 0 from ReadFileNative + // when we do async IO instead of the number of bytes read, + // irregardless of whether the operation completed + // synchronously or asynchronously. We absolutely must not + // set asyncResult._numBytes here, since will never have correct + // results. + //Console.WriteLine("ReadFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on a separate thread"); + } + + return asyncResult; + } + + [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed. + public unsafe override int EndRead(IAsyncResult asyncResult) + { + // There are 3 significantly different IAsyncResults we'll accept + // here. One is from Stream::BeginRead. The other two are variations + // on our FileStreamAsyncResult. One is from BeginReadCore, + // while the other is from the BeginRead buffering wrapper. + if (asyncResult==null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + if (!_isAsync) + return base.EndRead(asyncResult); + + FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult; + if (afsar==null || afsar.IsWrite) + __Error.WrongAsyncResult(); + + // Ensure we don't have any race conditions by doing an interlocked + // CompareExchange here. Avoids corrupting memory via freeing the + // NativeOverlapped class or GCHandle twice. -- + if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0)) + __Error.EndReadCalledTwice(); + + // Obtain the WaitHandle, but don't use public property in case we + // delay initialize the manual reset event in the future. + afsar.Wait(); + + // Free memory & GC handles. + afsar.ReleaseNativeResource(); + + // Now check for any error during the read. + if (afsar.ErrorCode != 0) + __Error.WinIOError(afsar.ErrorCode, String.Empty); + + return afsar.NumBytesRead; + } + + // Reads a byte from the file stream. Returns the byte cast to an int + // or -1 if reading from the end of the stream. + [System.Security.SecuritySafeCritical] // auto-generated + public override int ReadByte() { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (_readLen==0 && !CanRead) __Error.ReadNotSupported(); + Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + if (_readPos == _readLen) { + if (_writePos > 0) FlushWrite(false); + Contract.Assert(_bufferSize > 0, "_bufferSize > 0"); + if (_buffer == null) _buffer = new byte[_bufferSize]; + _readLen = ReadCore(_buffer, 0, _bufferSize); + _readPos = 0; + } + if (_readPos == _readLen) + return -1; + + int result = _buffer[_readPos]; + _readPos++; + return result; + } + + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading=true)] + public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + if (array==null) + throw new ArgumentNullException("array"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (numBytes < 0) + throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < numBytes) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + if (!_isAsync) + return base.BeginWrite(array, offset, numBytes, userCallback, stateObject); + else + return BeginWriteAsync(array, offset, numBytes, userCallback, stateObject); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading = true)] + private FileStreamAsyncResult BeginWriteAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + Contract.Assert(_isAsync); + + if (!CanWrite) __Error.WriteNotSupported(); + + Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + if (_isPipe) + { + // When redirecting stdout & stderr with the Process class, it's easy to deadlock your + // parent & child processes when doing writes 4K at a time. The + // OS appears to use a 4K buffer internally. If you write to a + // pipe that is full, you will block until someone read from + // that pipe. If you try reading from an empty pipe and + // FileStream's BeginRead blocks waiting for data to fill it's + // internal buffer, you will be blocked. In a case where a child + // process writes to stdout & stderr while a parent process tries + // reading from both, you can easily get into a deadlock here. + // To avoid this deadlock, don't buffer when doing async IO on + // pipes. + Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream must not have buffered data here! Pipes should be unidirectional."); + + if (_writePos > 0) + FlushWrite(false); + + return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); + } + + // Handle buffering. + if (_writePos == 0) + { + if (_readPos < _readLen) FlushRead(); + _readPos = 0; + _readLen = 0; + } + + int n = _bufferSize - _writePos; + if (numBytes <= n) + { + if (_writePos == 0) _buffer = new byte[_bufferSize]; + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes); + _writePos += numBytes; + + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(numBytes, userCallback, stateObject, true); + } + + if (_writePos > 0) + FlushWrite(false); + + return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); + } + + [System.Security.SecuritySafeCritical] // auto-generated + unsafe private FileStreamAsyncResult BeginWriteCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanWrite, "CanWrite"); + Contract.Assert(bytes != null, "bytes != null"); + Contract.Assert(_readPos == _readLen, "_readPos == _readLen"); + Contract.Assert(_isAsync, "BeginWriteCore doesn't work on synchronous file streams!"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(numBytes >= 0, "numBytes is negative"); + + // Create and store async stream class library specific data in the async result + FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(0, bytes, _handle, userCallback, stateObject, true); + NativeOverlapped* intOverlapped = asyncResult.OverLapped; + + if (CanSeek) { + // Make sure we set the length of the file appropriately. + long len = Length; + //Console.WriteLine("BeginWrite - Calculating end pos. pos: "+pos+" len: "+len+" numBytes: "+numBytes); + + // Make sure we are writing to the position that we think we are + if (_exposedHandle) + VerifyOSHandlePosition(); + + if (_pos + numBytes > len) { + //Console.WriteLine("BeginWrite - Setting length to: "+(pos + numBytes)); + SetLengthCore(_pos + numBytes); + } + + // Now set the position to read from in the NativeOverlapped struct + // For pipes, we should leave the offset fields set to 0. + intOverlapped->OffsetLow = (int)_pos; + intOverlapped->OffsetHigh = (int)(_pos>>32); + + // When using overlapped IO, the OS is not supposed to + // touch the file pointer location at all. We will adjust it + // ourselves. This isn't threadsafe. + + SeekCore(numBytes, SeekOrigin.Current); + } + + //Console.WriteLine("BeginWrite finishing. pos: "+pos+" numBytes: "+numBytes+" _pos: "+_pos+" Position: "+Position); + + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false); + + int hr = 0; + // queue an async WriteFile operation and pass in a packed overlapped + int r = WriteFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); + + // WriteFile, the OS version, will return 0 on failure. But + // my WriteFileNative wrapper returns -1. My wrapper will return + // the following: + // On error, r==-1. + // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING + // On async requests that completed sequentially, r==0 + // You will NEVER RELIABLY be able to get the number of bytes + // written back from this call when using overlapped IO! You must + // not pass in a non-null lpNumBytesWritten to WriteFile when using + // overlapped structures! This is ByDesign NT behavior. + if (r==-1 && numBytes!=-1) { + //Console.WriteLine("WriteFile returned 0; Write will complete asynchronously (if hr==3e5) hr: 0x{0:x}", hr); + + // For pipes, when they are closed on the other side, they will come here. + if (hr == ERROR_NO_DATA) { + // Not an error, but EOF. AsyncFSCallback will NOT be + // called. Call the user callback here. + asyncResult.CallUserCallback(); + // EndWrite will free the Overlapped struct correctly. + } + else if (hr != ERROR_IO_PENDING) { + if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. + SeekCore(0, SeekOrigin.Current); + + if (hr == ERROR_HANDLE_EOF) + __Error.EndOfFile(); + else + __Error.WinIOError(hr, String.Empty); + } + } + else { + // Due to a workaround for a race condition in NT's ReadFile & + // WriteFile routines, we will always be returning 0 from WriteFileNative + // when we do async IO instead of the number of bytes written, + // irregardless of whether the operation completed + // synchronously or asynchronously. We absolutely must not + // set asyncResult._numBytes here, since will never have correct + // results. + //Console.WriteLine("WriteFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on another thread."); + } + + return asyncResult; + } + + [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed. + public unsafe override void EndWrite(IAsyncResult asyncResult) + { + if (asyncResult==null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + if (!_isAsync) { + base.EndWrite(asyncResult); + return; + } + + FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult; + if (afsar==null || !afsar.IsWrite) + __Error.WrongAsyncResult(); + + // Ensure we can't have any race conditions by doing an interlocked + // CompareExchange here. Avoids corrupting memory via freeing the + // NativeOverlapped class or GCHandle twice. -- + if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0)) + __Error.EndWriteCalledTwice(); + + // Obtain the WaitHandle, but don't use public property in case we + // delay initialize the manual reset event in the future. + afsar.Wait(); + + // Free memory & GC handles. + afsar.ReleaseNativeResource(); + + // Now check for any error during the write. + if (afsar.ErrorCode != 0) + __Error.WinIOError(afsar.ErrorCode, String.Empty); + + // Number of bytes written is afsar._numBytes + afsar._numBufferedBytes. + return; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void WriteByte(byte value) + { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (_writePos==0) { + if (!CanWrite) __Error.WriteNotSupported(); + if (_readPos < _readLen) FlushRead(); + _readPos = 0; + _readLen = 0; + Contract.Assert(_bufferSize > 0, "_bufferSize > 0"); + if (_buffer==null) _buffer = new byte[_bufferSize]; + } + if (_writePos == _bufferSize) + FlushWrite(false); + + _buffer[_writePos] = value; + _writePos++; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public virtual void Lock(long position, long length) { + if (position < 0 || length < 0) + throw new ArgumentOutOfRangeException((position < 0 ? "position" : "length"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_handle.IsClosed) __Error.FileNotOpen(); + + int positionLow = unchecked((int)(position )); + int positionHigh = unchecked((int)(position >> 32)); + int lengthLow = unchecked((int)(length )); + int lengthHigh = unchecked((int)(length >> 32)); + + if (!Win32Native.LockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh)) + __Error.WinIOError(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public virtual void Unlock(long position, long length) { + if (position < 0 || length < 0) + throw new ArgumentOutOfRangeException((position < 0 ? "position" : "length"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_handle.IsClosed) __Error.FileNotOpen(); + + int positionLow = unchecked((int)(position )); + int positionHigh = unchecked((int)(position >> 32)); + int lengthLow = unchecked((int)(length )); + int lengthHigh = unchecked((int)(length >> 32)); + + if (!Win32Native.UnlockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh)) + __Error.WinIOError(); + } + + // Windows API definitions, from winbase.h and others + + private const int FILE_ATTRIBUTE_NORMAL = 0x00000080; + private const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; + private const int FILE_FLAG_OVERLAPPED = 0x40000000; + internal const int GENERIC_READ = unchecked((int)0x80000000); + private const int GENERIC_WRITE = 0x40000000; + + private const int FILE_BEGIN = 0; + private const int FILE_CURRENT = 1; + private const int FILE_END = 2; + + // Error codes (not HRESULTS), from winerror.h + internal const int ERROR_BROKEN_PIPE = 109; + internal const int ERROR_NO_DATA = 232; + private const int ERROR_HANDLE_EOF = 38; + private const int ERROR_INVALID_PARAMETER = 87; + private const int ERROR_IO_PENDING = 997; + + + // __ConsoleStream also uses this code. + [System.Security.SecurityCritical] // auto-generated + private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) + { + Contract.Requires(handle != null, "handle != null"); + Contract.Requires(offset >= 0, "offset >= 0"); + Contract.Requires(count >= 0, "count >= 0"); + Contract.Requires(bytes != null, "bytes != null"); + // Don't corrupt memory when multiple threads are erroneously writing + // to this stream simultaneously. + if (bytes.Length - offset < count) + throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition")); + Contract.EndContractBlock(); + + Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter mismatch in call to ReadFileNative."); + + // You can't use the fixed statement on an array of length 0. + if (bytes.Length==0) { + hr = 0; + return 0; + } + + int r = 0; + int numBytesRead = 0; + + fixed(byte* p = bytes) { + if (_isAsync) + r = Win32Native.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped); + else + r = Win32Native.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero); + } + + if (r==0) { + hr = Marshal.GetLastWin32Error(); + // We should never silently drop an error here without some + // extra work. We must make sure that BeginReadCore won't return an + // IAsyncResult that will cause EndRead to block, since the OS won't + // call AsyncFSCallback for us. + if (hr == ERROR_BROKEN_PIPE || hr == Win32Native.ERROR_PIPE_NOT_CONNECTED) { + // This handle was a pipe, and it's done. Not an error, but EOF. + // However, the OS will not call AsyncFSCallback! + // Let the caller handle this, since BeginReadCore & ReadCore + // need to do different things. + return -1; + } + + // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)". + if (hr == Win32Native.ERROR_INVALID_HANDLE) + _handle.Dispose(); + + return -1; + } + else + hr = 0; + return numBytesRead; + } + + [System.Security.SecurityCritical] // auto-generated + private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) { + Contract.Requires(handle != null, "handle != null"); + Contract.Requires(offset >= 0, "offset >= 0"); + Contract.Requires(count >= 0, "count >= 0"); + Contract.Requires(bytes != null, "bytes != null"); + // Don't corrupt memory when multiple threads are erroneously writing + // to this stream simultaneously. (the OS is reading from + // the array we pass to WriteFile, but if we read beyond the end and + // that memory isn't allocated, we could get an AV.) + if (bytes.Length - offset < count) + throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition")); + Contract.EndContractBlock(); + + Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter missmatch in call to WriteFileNative."); + + // You can't use the fixed statement on an array of length 0. + if (bytes.Length==0) { + hr = 0; + return 0; + } + + int numBytesWritten = 0; + int r = 0; + + fixed(byte* p = bytes) { + if (_isAsync) + r = Win32Native.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped); + else + r = Win32Native.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero); + } + + if (r==0) { + hr = Marshal.GetLastWin32Error(); + // We should never silently drop an error here without some + // extra work. We must make sure that BeginWriteCore won't return an + // IAsyncResult that will cause EndWrite to block, since the OS won't + // call AsyncFSCallback for us. + + if (hr==ERROR_NO_DATA) { + // This handle was a pipe, and the pipe is being closed on the + // other side. Let the caller handle this, since BeginWriteCore + // & WriteCore need to do different things. + return -1; + } + + // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)". + if (hr == Win32Native.ERROR_INVALID_HANDLE) + _handle.Dispose(); + + return -1; + } + else + hr = 0; + return numBytesWritten; + } + + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + [SecuritySafeCritical] + public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Read() or BeginRead() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read/BeginRead) when we are not sure. + if (this.GetType() != typeof(FileStream)) + return base.ReadAsync(buffer, offset, count, cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<int>(cancellationToken); + + if (_handle.IsClosed) + __Error.FileNotOpen(); + + // If async IO is not supported on this platform or + // if this FileStream was not opened with FileOptions.Asynchronous. + if (!_isAsync) + return base.ReadAsync(buffer, offset, count, cancellationToken); + + var readTask = new FileStreamReadWriteTask<int>(cancellationToken); + var endReadTask = s_endReadTask; + if (endReadTask == null) s_endReadTask = endReadTask = EndReadTask; // benign initialization race condition + readTask._asyncResult = BeginReadAsync(buffer, offset, count, endReadTask, readTask); + + if (readTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled) + { + var cancelReadHandler = s_cancelReadHandler; + if (cancelReadHandler == null) s_cancelReadHandler = cancelReadHandler = CancelTask<int>; // benign initialization race condition + readTask._registration = cancellationToken.Register(cancelReadHandler, readTask); + + // In case the task is completed right before we register the cancellation callback. + if (readTask._asyncResult.IsCompleted) + readTask._registration.Dispose(); + } + + return readTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + [SecuritySafeCritical] + public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Write() or BeginWrite() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write/BeginWrite) when we are not sure. + if (this.GetType() != typeof(FileStream)) + return base.WriteAsync(buffer, offset, count, cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (_handle.IsClosed) + __Error.FileNotOpen(); + + // If async IO is not supported on this platform or + // if this FileStream was not opened with FileOptions.Asynchronous. + if (!_isAsync) + return base.WriteAsync(buffer, offset, count, cancellationToken); + + var writeTask = new FileStreamReadWriteTask<VoidTaskResult>(cancellationToken); + var endWriteTask = s_endWriteTask; + if (endWriteTask == null) s_endWriteTask = endWriteTask = EndWriteTask; // benign initialization race condition + writeTask._asyncResult = BeginWriteAsync(buffer, offset, count, endWriteTask, writeTask); + + if (writeTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled) + { + var cancelWriteHandler = s_cancelWriteHandler; + if (cancelWriteHandler == null) s_cancelWriteHandler = cancelWriteHandler = CancelTask<VoidTaskResult>; // benign initialization race condition + writeTask._registration = cancellationToken.Register(cancelWriteHandler, writeTask); + + // In case the task is completed right before we register the cancellation callback. + if (writeTask._asyncResult.IsCompleted) + writeTask._registration.Dispose(); + } + + return writeTask; + } + + // The task instance returned from ReadAsync and WriteAsync. + // Also stores all of the state necessary for those calls to avoid closures and extraneous delegate allocations. + private sealed class FileStreamReadWriteTask<T> : Task<T> + { + internal CancellationToken _cancellationToken; + internal CancellationTokenRegistration _registration; + internal FileStreamAsyncResult _asyncResult; // initialized after Begin call completes + + internal FileStreamReadWriteTask(CancellationToken cancellationToken) : base() + { + _cancellationToken = cancellationToken; + } + } + + // Cancellation callback for both ReadAsync and WriteAsync. + [SecuritySafeCritical] + private static void CancelTask<T>(object state) + { + var task = state as FileStreamReadWriteTask<T>; + Contract.Assert(task != null); + FileStreamAsyncResult asyncResult = task._asyncResult; + + // This method is used as both the completion callback and the cancellation callback. + // We should try to cancel the operation if this is running as the completion callback + // or if cancellation is not applicable: + // 1. asyncResult is not a FileStreamAsyncResult + // 2. asyncResult.IsAsync is false: asyncResult is a "synchronous" FileStreamAsyncResult. + // 3. The asyncResult is completed: this should never happen. + Contract.Assert((!asyncResult.IsWrite && typeof(T) == typeof(int)) || + (asyncResult.IsWrite && typeof(T) == typeof(VoidTaskResult))); + Contract.Assert(asyncResult != null); + Contract.Assert(asyncResult.IsAsync); + + try + { + // Cancel the overlapped read and set the task to cancelled state. + if (!asyncResult.IsCompleted) + asyncResult.Cancel(); + } + catch (Exception ex) + { + task.TrySetException(ex); + } + } + + // Completion callback for ReadAsync + [SecuritySafeCritical] + private static void EndReadTask(IAsyncResult iar) + { + FileStreamAsyncResult asyncResult = iar as FileStreamAsyncResult; + Contract.Assert(asyncResult != null); + Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?"); + + var readTask = asyncResult.AsyncState as FileStreamReadWriteTask<int>; + Contract.Assert(readTask != null); + + try + { + if (asyncResult.IsAsync) + { + asyncResult.ReleaseNativeResource(); + + // release the resource held by CancellationTokenRegistration + readTask._registration.Dispose(); + } + + if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED) + { + var cancellationToken = readTask._cancellationToken; + Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?"); + readTask.TrySetCanceled(cancellationToken); + } + else + readTask.TrySetResult(asyncResult.NumBytesRead); + } + catch (Exception ex) + { + readTask.TrySetException(ex); + } + } + + // Completion callback for WriteAsync + [SecuritySafeCritical] + private static void EndWriteTask(IAsyncResult iar) + { + var asyncResult = iar as FileStreamAsyncResult; + Contract.Assert(asyncResult != null); + Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?"); + + var writeTask = iar.AsyncState as FileStreamReadWriteTask<VoidTaskResult>; + Contract.Assert(writeTask != null); + + try + { + if (asyncResult.IsAsync) + { + asyncResult.ReleaseNativeResource(); + + // release the resource held by CancellationTokenRegistration + writeTask._registration.Dispose(); + } + + if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED) + { + var cancellationToken = writeTask._cancellationToken; + Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?"); + writeTask.TrySetCanceled(cancellationToken); + } + else + writeTask.TrySetResult(default(VoidTaskResult)); + } + catch (Exception ex) + { + writeTask.TrySetException(ex); + } + } + + // Unlike Flush(), FlushAsync() always flushes to disk. This is intentional. + // Legend is that we chose not to flush the OS file buffers in Flush() in fear of + // perf problems with frequent, long running FlushFileBuffers() calls. But we don't + // have that problem with FlushAsync() because we will call FlushFileBuffers() in the background. + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + [System.Security.SecuritySafeCritical] + public override Task FlushAsync(CancellationToken cancellationToken) + { + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Flush() which a subclass might have overriden. To be safe + // we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Flush) when we are not sure. + if (this.GetType() != typeof(FileStream)) + return base.FlushAsync(cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (_handle.IsClosed) + __Error.FileNotOpen(); + + // The always synchronous data transfer between the OS and the internal buffer is intentional + // because this is needed to allow concurrent async IO requests. Concurrent data transfer + // between the OS and the internal buffer will result in race conditions. Since FlushWrite and + // FlushRead modify internal state of the stream and transfer data between the OS and the + // internal buffer, they cannot be truly async. We will, however, flush the OS file buffers + // asynchronously because it doesn't modify any internal state of the stream and is potentially + // a long running process. + try + { + FlushInternalBuffer(); + } + catch (Exception e) + { + return Task.FromException(e); + } + + if (CanWrite) + return Task.Factory.StartNew( + state => ((FileStream)state).FlushOSBuffer(), + this, + cancellationToken, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default); + else + return Task.CompletedTask; + } + + } +} diff --git a/src/mscorlib/src/System/IO/FileSystemEnumerable.cs b/src/mscorlib/src/System/IO/FileSystemEnumerable.cs new file mode 100644 index 0000000000..c2e603c06a --- /dev/null +++ b/src/mscorlib/src/System/IO/FileSystemEnumerable.cs @@ -0,0 +1,852 @@ +// 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: Enumerates files and dirs +** +===========================================================*/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Security; +using System.Security.Permissions; +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; +using System.Text; +using System.Runtime.InteropServices; +using System.Globalization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; +using System.Threading; + +namespace System.IO +{ + + // Overview: + // The key methods instantiate FileSystemEnumerableIterators. These compose the iterator with search result + // handlers that instantiate the FileInfo, DirectoryInfo, String, etc. The handlers then perform any + // additional required permission demands. + internal static class FileSystemEnumerableFactory + { + internal static IEnumerable<String> CreateFileNameIterator(String path, String originalUserPath, String searchPattern, + bool includeFiles, bool includeDirs, SearchOption searchOption, bool checkHost) + { + Contract.Requires(path != null); + Contract.Requires(originalUserPath != null); + Contract.Requires(searchPattern != null); + + SearchResultHandler<String> handler = new StringResultHandler(includeFiles, includeDirs); + return new FileSystemEnumerableIterator<String>(path, originalUserPath, searchPattern, searchOption, handler, checkHost); + } + + internal static IEnumerable<FileInfo> CreateFileInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(originalUserPath != null); + Contract.Requires(searchPattern != null); + + SearchResultHandler<FileInfo> handler = new FileInfoResultHandler(); + return new FileSystemEnumerableIterator<FileInfo>(path, originalUserPath, searchPattern, searchOption, handler, true); + } + + internal static IEnumerable<DirectoryInfo> CreateDirectoryInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption) + { + + Contract.Requires(path != null); + Contract.Requires(originalUserPath != null); + Contract.Requires(searchPattern != null); + + SearchResultHandler<DirectoryInfo> handler = new DirectoryInfoResultHandler(); + return new FileSystemEnumerableIterator<DirectoryInfo>(path, originalUserPath, searchPattern, searchOption, handler, true); + } + + internal static IEnumerable<FileSystemInfo> CreateFileSystemInfoIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption) + { + Contract.Requires(path != null); + Contract.Requires(originalUserPath != null); + Contract.Requires(searchPattern != null); + + SearchResultHandler<FileSystemInfo> handler = new FileSystemInfoResultHandler(); + return new FileSystemEnumerableIterator<FileSystemInfo>(path, originalUserPath, searchPattern, searchOption, handler, true); + } + } + + // Abstract Iterator, borrowed from Linq. Used in anticipation of need for similar enumerables + // in the future + abstract internal class Iterator<TSource> : IEnumerable<TSource>, IEnumerator<TSource> + { + int threadId; + internal int state; + internal TSource current; + + public Iterator() + { + threadId = Thread.CurrentThread.ManagedThreadId; + } + + public TSource Current + { + get { return current; } + } + + protected abstract Iterator<TSource> Clone(); + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + current = default(TSource); + state = -1; + } + + public IEnumerator<TSource> GetEnumerator() + { + if (threadId == Thread.CurrentThread.ManagedThreadId && state == 0) + { + state = 1; + return this; + } + + Iterator<TSource> duplicate = Clone(); + duplicate.state = 1; + return duplicate; + } + + public abstract bool MoveNext(); + + object IEnumerator.Current + { + get { return Current; } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + void IEnumerator.Reset() + { + throw new NotSupportedException(); + } + + } + + // Overview: + // Enumerates file system entries matching the search parameters. For recursive searches this + // searches through all the sub dirs and executes the search criteria against every dir. + // + // Generic implementation: + // FileSystemEnumerableIterator is generic. When it gets a WIN32_FIND_DATA, it calls the + // result handler to create an instance of the generic type. + // + // Usage: + // Use FileSystemEnumerableFactory to obtain FSEnumerables that can enumerate file system + // entries as String path names, FileInfos, DirectoryInfos, or FileSystemInfos. + // + // Security: + // For all the dirs/files returned, demands path discovery permission for their parent folders + internal class FileSystemEnumerableIterator<TSource> : Iterator<TSource> + { + + private const int STATE_INIT = 1; + private const int STATE_SEARCH_NEXT_DIR = 2; + private const int STATE_FIND_NEXT_FILE = 3; + private const int STATE_FINISH = 4; + + private SearchResultHandler<TSource> _resultHandler; + private List<Directory.SearchData> searchStack; + private Directory.SearchData searchData; + private String searchCriteria; + [System.Security.SecurityCritical] + SafeFindHandle _hnd = null; + bool needsParentPathDiscoveryDemand; + + // empty means we know in advance that we won’t find any search results, which can happen if: + // 1. we don’t have a search pattern + // 2. we’re enumerating only the top directory and found no matches during the first call + // This flag allows us to return early for these cases. We can’t know this in advance for + // SearchOption.AllDirectories because we do a “*” search for subdirs and then use the + // searchPattern at each directory level. + bool empty; + + private String userPath; + private SearchOption searchOption; + private String fullPath; + private String normalizedSearchPath; + private int oldMode; + private bool _checkHost; + + [System.Security.SecuritySafeCritical] + internal FileSystemEnumerableIterator(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost) + { + Contract.Requires(path != null); + Contract.Requires(originalUserPath != null); + Contract.Requires(searchPattern != null); + Contract.Requires(searchOption == SearchOption.AllDirectories || searchOption == SearchOption.TopDirectoryOnly); + Contract.Requires(resultHandler != null); + + oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + + searchStack = new List<Directory.SearchData>(); + + String normalizedSearchPattern = NormalizeSearchPattern(searchPattern); + + if (normalizedSearchPattern.Length == 0) + { + empty = true; + } + else + { + _resultHandler = resultHandler; + this.searchOption = searchOption; + + fullPath = Path.GetFullPathInternal(path); + String fullSearchString = GetFullSearchString(fullPath, normalizedSearchPattern); + normalizedSearchPath = Path.GetDirectoryName(fullSearchString); + + // permission demands + String[] demandPaths = new String[2]; + // Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters + demandPaths[0] = Directory.GetDemandDir(fullPath, true); + // For filters like foo\*.cs we need to verify if the directory foo is not denied access. + // Do a demand on the combined path so that we can fail early in case of deny + demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true); + _checkHost = checkHost; +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state1 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[0]); + state1.EnsureState(); + FileSecurityState state2 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[1]); + state2.EnsureState(); + } +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false).Demand(); +#endif + + // normalize search criteria + searchCriteria = GetNormalizedSearchCriteria(fullSearchString, normalizedSearchPath); + + // fix up user path + String searchPatternDirName = Path.GetDirectoryName(normalizedSearchPattern); + String userPathTemp = originalUserPath; + if (searchPatternDirName != null && searchPatternDirName.Length != 0) + { + userPathTemp = Path.Combine(userPathTemp, searchPatternDirName); + } + this.userPath = userPathTemp; + + searchData = new Directory.SearchData(normalizedSearchPath, this.userPath, searchOption); + + CommonInit(); + } + + } + + [System.Security.SecurityCritical] + private void CommonInit() + { + Contract.Assert(searchCriteria != null && searchData != null, "searchCriteria and searchData should be initialized"); + + // Execute searchCriteria against the current directory + String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria); + + Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); + + // Open a Find handle + _hnd = Win32Native.FindFirstFile(searchPath, data); + + if (_hnd.IsInvalid) + { + int hr = Marshal.GetLastWin32Error(); + if (hr != Win32Native.ERROR_FILE_NOT_FOUND && hr != Win32Native.ERROR_NO_MORE_FILES) + { + HandleError(hr, searchData.fullPath); + } + else + { + // flag this as empty only if we're searching just top directory + // Used in fast path for top directory only + empty = searchData.searchOption == SearchOption.TopDirectoryOnly; + } + } + // fast path for TopDirectoryOnly. If we have a result, go ahead and set it to + // current. If empty, dispose handle. + if (searchData.searchOption == SearchOption.TopDirectoryOnly) + { + if (empty) + { + _hnd.Dispose(); + } + else + { + SearchResult searchResult = CreateSearchResult(searchData, data); + if (_resultHandler.IsResultIncluded(searchResult)) + { + current = _resultHandler.CreateObject(searchResult); + } + } + } + // for AllDirectories, we first recurse into dirs, so cleanup and add searchData + // to the stack + else + { + _hnd.Dispose(); + searchStack.Add(searchData); + } + } + + [System.Security.SecuritySafeCritical] + private FileSystemEnumerableIterator(String fullPath, String normalizedSearchPath, String searchCriteria, String userPath, SearchOption searchOption, SearchResultHandler<TSource> resultHandler, bool checkHost) + { + this.fullPath = fullPath; + this.normalizedSearchPath = normalizedSearchPath; + this.searchCriteria = searchCriteria; + this._resultHandler = resultHandler; + this.userPath = userPath; + this.searchOption = searchOption; + this._checkHost = checkHost; + + searchStack = new List<Directory.SearchData>(); + + if (searchCriteria != null) + { + // permission demands + String[] demandPaths = new String[2]; + // Any illegal chars such as *, ? will be caught by FileIOPermission.HasIllegalCharacters + demandPaths[0] = Directory.GetDemandDir(fullPath, true); + // For filters like foo\*.cs we need to verify if the directory foo is not denied access. + // Do a demand on the combined path so that we can fail early in case of deny + demandPaths[1] = Directory.GetDemandDir(normalizedSearchPath, true); +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state1 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[0]); + state1.EnsureState(); + FileSecurityState state2 = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandPaths[1]); + state2.EnsureState(); + } +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false).Demand(); +#endif + searchData = new Directory.SearchData(normalizedSearchPath, userPath, searchOption); + CommonInit(); + } + else + { + empty = true; + } + } + + protected override Iterator<TSource> Clone() + { + return new FileSystemEnumerableIterator<TSource>(fullPath, normalizedSearchPath, searchCriteria, userPath, searchOption, _resultHandler, _checkHost); + } + + [System.Security.SecuritySafeCritical] + protected override void Dispose(bool disposing) + { + try + { + if (_hnd != null) + { + _hnd.Dispose(); + } + } + finally + { + Win32Native.SetErrorMode(oldMode); + base.Dispose(disposing); + } + } + + [System.Security.SecuritySafeCritical] + public override bool MoveNext() + { + Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); + switch (state) + { + case STATE_INIT: + { + if (empty) + { + state = STATE_FINISH; + goto case STATE_FINISH; + } + if (searchData.searchOption == SearchOption.TopDirectoryOnly) + { + state = STATE_FIND_NEXT_FILE; + if (current != null) + { + return true; + } + else + { + goto case STATE_FIND_NEXT_FILE; + } + } + else + { + state = STATE_SEARCH_NEXT_DIR; + goto case STATE_SEARCH_NEXT_DIR; + } + } + case STATE_SEARCH_NEXT_DIR: + { + Contract.Assert(searchData.searchOption != SearchOption.TopDirectoryOnly, "should not reach this code path if searchOption == TopDirectoryOnly"); + // Traverse directory structure. We need to get '*' + while (searchStack.Count > 0) + { + searchData = searchStack[0]; + Contract.Assert((searchData.fullPath != null), "fullpath can't be null!"); + searchStack.RemoveAt(0); + + // Traverse the subdirs + AddSearchableDirsToStack(searchData); + + // Execute searchCriteria against the current directory + String searchPath = Path.InternalCombine(searchData.fullPath, searchCriteria); + + // Open a Find handle + _hnd = Win32Native.FindFirstFile(searchPath, data); + if (_hnd.IsInvalid) + { + int hr = Marshal.GetLastWin32Error(); + if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND) + continue; + + _hnd.Dispose(); + HandleError(hr, searchData.fullPath); + } + + state = STATE_FIND_NEXT_FILE; + needsParentPathDiscoveryDemand = true; + SearchResult searchResult = CreateSearchResult(searchData, data); + if (_resultHandler.IsResultIncluded(searchResult)) + { + if (needsParentPathDiscoveryDemand) + { + DoDemand(searchData.fullPath); + needsParentPathDiscoveryDemand = false; + } + current = _resultHandler.CreateObject(searchResult); + return true; + } + else + { + goto case STATE_FIND_NEXT_FILE; + } + } + state = STATE_FINISH; + goto case STATE_FINISH; + } + case STATE_FIND_NEXT_FILE: + { + if (searchData != null && _hnd != null) + { + // Keep asking for more matching files/dirs, add it to the list + while (Win32Native.FindNextFile(_hnd, data)) + { + SearchResult searchResult = CreateSearchResult(searchData, data); + if (_resultHandler.IsResultIncluded(searchResult)) + { + if (needsParentPathDiscoveryDemand) + { + DoDemand(searchData.fullPath); + needsParentPathDiscoveryDemand = false; + } + current = _resultHandler.CreateObject(searchResult); + return true; + } + } + + // Make sure we quit with a sensible error. + int hr = Marshal.GetLastWin32Error(); + + if (_hnd != null) + _hnd.Dispose(); + + // ERROR_FILE_NOT_FOUND is valid here because if the top level + // dir doen't contain any subdirs and matching files then + // we will get here with this errorcode from the searchStack walk + if ((hr != 0) && (hr != Win32Native.ERROR_NO_MORE_FILES) + && (hr != Win32Native.ERROR_FILE_NOT_FOUND)) + { + HandleError(hr, searchData.fullPath); + } + } + if (searchData.searchOption == SearchOption.TopDirectoryOnly) + { + state = STATE_FINISH; + goto case STATE_FINISH; + } + else + { + state = STATE_SEARCH_NEXT_DIR; + goto case STATE_SEARCH_NEXT_DIR; + } + } + case STATE_FINISH: + { + Dispose(); + break; + } + } + return false; + } + + [System.Security.SecurityCritical] + private SearchResult CreateSearchResult(Directory.SearchData localSearchData, Win32Native.WIN32_FIND_DATA findData) + { + String userPathFinal = Path.InternalCombine(localSearchData.userPath, findData.cFileName); + String fullPathFinal = Path.InternalCombine(localSearchData.fullPath, findData.cFileName); + return new SearchResult(fullPathFinal, userPathFinal, findData); + } + + [System.Security.SecurityCritical] + private void HandleError(int hr, String path) + { + Dispose(); + __Error.WinIOError(hr, path); + } + + [System.Security.SecurityCritical] // auto-generated + private void AddSearchableDirsToStack(Directory.SearchData localSearchData) + { + Contract.Requires(localSearchData != null); + + String searchPath = Path.InternalCombine(localSearchData.fullPath, "*"); + SafeFindHandle hnd = null; + Win32Native.WIN32_FIND_DATA data = new Win32Native.WIN32_FIND_DATA(); + try + { + // Get all files and dirs + hnd = Win32Native.FindFirstFile(searchPath, data); + + if (hnd.IsInvalid) + { + int hr = Marshal.GetLastWin32Error(); + + // This could happen if the dir doesn't contain any files. + // Continue with the recursive search though, eventually + // searchStack will become empty + if (hr == Win32Native.ERROR_FILE_NOT_FOUND || hr == Win32Native.ERROR_NO_MORE_FILES || hr == Win32Native.ERROR_PATH_NOT_FOUND) + return; + + HandleError(hr, localSearchData.fullPath); + } + + // Add subdirs to searchStack. Exempt ReparsePoints as appropriate + int incr = 0; + do + { + if (FileSystemEnumerableHelpers.IsDir(data)) + { + String tempFullPath = Path.InternalCombine(localSearchData.fullPath, data.cFileName); + String tempUserPath = Path.InternalCombine(localSearchData.userPath, data.cFileName); + + SearchOption option = localSearchData.searchOption; + +#if EXCLUDE_REPARSEPOINTS + // Traverse reparse points depending on the searchoption specified + if ((searchDataSubDir.searchOption == SearchOption.AllDirectories) && (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_REPARSE_POINT))) + option = SearchOption.TopDirectoryOnly; +#endif + // Setup search data for the sub directory and push it into the stack + Directory.SearchData searchDataSubDir = new Directory.SearchData(tempFullPath, tempUserPath, option); + + searchStack.Insert(incr++, searchDataSubDir); + } + } while (Win32Native.FindNextFile(hnd, data)); + // We don't care about errors here + } + finally + { + if (hnd != null) + hnd.Dispose(); + } + } + + [System.Security.SecurityCritical] + internal void DoDemand(String fullPathToDemand) + { +#if FEATURE_CORECLR + if(_checkHost) { + String demandDir = Directory.GetDemandDir(fullPathToDemand, true); + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandDir); + state.EnsureState(); + } +#else + String demandDir = Directory.GetDemandDir(fullPathToDemand, true); + String[] demandPaths = new String[] { demandDir }; + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandPaths, false, false).Demand(); +#endif + } + + private static String NormalizeSearchPattern(String searchPattern) + { + Contract.Requires(searchPattern != null); + + // Win32 normalization trims only U+0020. + String tempSearchPattern = searchPattern.TrimEnd(Path.TrimEndChars); + + // Make this corner case more useful, like dir + if (tempSearchPattern.Equals(".")) + { + tempSearchPattern = "*"; + } + + Path.CheckSearchPattern(tempSearchPattern); + return tempSearchPattern; + } + + private static String GetNormalizedSearchCriteria(String fullSearchString, String fullPathMod) + { + Contract.Requires(fullSearchString != null); + Contract.Requires(fullPathMod != null); + Contract.Requires(fullSearchString.Length >= fullPathMod.Length); + + String searchCriteria = null; + char lastChar = fullPathMod[fullPathMod.Length - 1]; + if (Path.IsDirectorySeparator(lastChar)) + { + // Can happen if the path is C:\temp, in which case GetDirectoryName would return C:\ + searchCriteria = fullSearchString.Substring(fullPathMod.Length); + } + else + { + Contract.Assert(fullSearchString.Length > fullPathMod.Length); + searchCriteria = fullSearchString.Substring(fullPathMod.Length + 1); + } + return searchCriteria; + } + + private static String GetFullSearchString(String fullPath, String searchPattern) + { + Contract.Requires(fullPath != null); + Contract.Requires(searchPattern != null); + + String tempStr = Path.InternalCombine(fullPath, searchPattern); + + // If path ends in a trailing slash (\), append a * or we'll get a "Cannot find the file specified" exception + char lastChar = tempStr[tempStr.Length - 1]; + if (Path.IsDirectorySeparator(lastChar) || lastChar == Path.VolumeSeparatorChar) + { + tempStr = tempStr + '*'; + } + + return tempStr; + } + } + + internal abstract class SearchResultHandler<TSource> + { + + [System.Security.SecurityCritical] + internal abstract bool IsResultIncluded(SearchResult result); + + [System.Security.SecurityCritical] + internal abstract TSource CreateObject(SearchResult result); + + } + + internal class StringResultHandler : SearchResultHandler<String> + { + private bool _includeFiles; + private bool _includeDirs; + + internal StringResultHandler(bool includeFiles, bool includeDirs) + { + _includeFiles = includeFiles; + _includeDirs = includeDirs; + } + + [System.Security.SecurityCritical] + internal override bool IsResultIncluded(SearchResult result) + { + bool includeFile = _includeFiles && FileSystemEnumerableHelpers.IsFile(result.FindData); + bool includeDir = _includeDirs && FileSystemEnumerableHelpers.IsDir(result.FindData); + Contract.Assert(!(includeFile && includeDir), result.FindData.cFileName + ": current item can't be both file and dir!"); + return (includeFile || includeDir); + } + + [System.Security.SecurityCritical] + internal override String CreateObject(SearchResult result) + { + return result.UserPath; + } + } + + internal class FileInfoResultHandler : SearchResultHandler<FileInfo> + { + [System.Security.SecurityCritical] + internal override bool IsResultIncluded(SearchResult result) + { + return FileSystemEnumerableHelpers.IsFile(result.FindData); + } + + [System.Security.SecurityCritical] + internal override FileInfo CreateObject(SearchResult result) + { + String name = result.FullPath; +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, name); + state.EnsureState(); +#else + String[] names = new String[] { name }; + new FileIOPermission(FileIOPermissionAccess.Read, names, false, false).Demand(); +#endif + FileInfo fi = new FileInfo(name, false); + fi.InitializeFrom(result.FindData); + return fi; + } + } + + internal class DirectoryInfoResultHandler : SearchResultHandler<DirectoryInfo> + { + [System.Security.SecurityCritical] + internal override bool IsResultIncluded(SearchResult result) + { + return FileSystemEnumerableHelpers.IsDir(result.FindData); + } + + [System.Security.SecurityCritical] + internal override DirectoryInfo CreateObject(SearchResult result) + { + String name = result.FullPath; + String permissionName = name + "\\."; + +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, permissionName); + state.EnsureState(); +#else + String[] permissionNames = new String[] { permissionName }; + new FileIOPermission(FileIOPermissionAccess.Read, permissionNames, false, false).Demand(); +#endif + DirectoryInfo di = new DirectoryInfo(name, false); + di.InitializeFrom(result.FindData); + return di; + } + } + + internal class FileSystemInfoResultHandler : SearchResultHandler<FileSystemInfo> + { + + [System.Security.SecurityCritical] + internal override bool IsResultIncluded(SearchResult result) + { + bool includeFile = FileSystemEnumerableHelpers.IsFile(result.FindData); + bool includeDir = FileSystemEnumerableHelpers.IsDir(result.FindData); + Contract.Assert(!(includeFile && includeDir), result.FindData.cFileName + ": current item can't be both file and dir!"); + + return (includeDir || includeFile); + } + + [System.Security.SecurityCritical] + internal override FileSystemInfo CreateObject(SearchResult result) + { + bool isFile = FileSystemEnumerableHelpers.IsFile(result.FindData); + bool isDir = FileSystemEnumerableHelpers.IsDir(result.FindData); + + if (isDir) + { + String name = result.FullPath; + String permissionName = name + "\\."; + +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, permissionName); + state.EnsureState(); +#else + String[] permissionNames = new String[] { permissionName }; + new FileIOPermission(FileIOPermissionAccess.Read, permissionNames, false, false).Demand(); +#endif + DirectoryInfo di = new DirectoryInfo(name, false); + di.InitializeFrom(result.FindData); + return di; + } + else + { + Contract.Assert(isFile); + String name = result.FullPath; + +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, name); + state.EnsureState(); +#else + String[] names = new String[] { name }; + new FileIOPermission(FileIOPermissionAccess.Read, names, false, false).Demand(); +#endif + FileInfo fi = new FileInfo(name, false); + fi.InitializeFrom(result.FindData); + return fi; + } + } + + } + + internal sealed class SearchResult + { + private String fullPath; // fully-qualifed path + private String userPath; // user-specified path + [System.Security.SecurityCritical] + private Win32Native.WIN32_FIND_DATA findData; + + [System.Security.SecurityCritical] + internal SearchResult(String fullPath, String userPath, Win32Native.WIN32_FIND_DATA findData) + { + Contract.Requires(fullPath != null); + Contract.Requires(userPath != null); + + this.fullPath = fullPath; + this.userPath = userPath; + this.findData = findData; + } + + internal String FullPath + { + get { return fullPath; } + } + + internal String UserPath + { + get { return userPath; } + } + + internal Win32Native.WIN32_FIND_DATA FindData + { + [System.Security.SecurityCritical] + get { return findData; } + } + + } + + internal static class FileSystemEnumerableHelpers + { + [System.Security.SecurityCritical] // auto-generated + internal static bool IsDir(Win32Native.WIN32_FIND_DATA data) + { + // Don't add "." nor ".." + return (0 != (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY)) + && !data.cFileName.Equals(".") && !data.cFileName.Equals(".."); + } + + [System.Security.SecurityCritical] // auto-generated + internal static bool IsFile(Win32Native.WIN32_FIND_DATA data) + { + return 0 == (data.dwFileAttributes & Win32Native.FILE_ATTRIBUTE_DIRECTORY); + } + + } +} + diff --git a/src/mscorlib/src/System/IO/FileSystemInfo.cs b/src/mscorlib/src/System/IO/FileSystemInfo.cs new file mode 100644 index 0000000000..7a17a417af --- /dev/null +++ b/src/mscorlib/src/System/IO/FileSystemInfo.cs @@ -0,0 +1,361 @@ +// 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: +** +** +===========================================================*/ + +using System; +using System.Collections; +using System.Security; +using System.Security.Permissions; +using Microsoft.Win32; +using System.Text; +using System.Runtime.InteropServices; +using System.Runtime.Serialization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; + +namespace System.IO { +#if FEATURE_SERIALIZATION + [Serializable] +#endif +#if !FEATURE_CORECLR + [FileIOPermissionAttribute(SecurityAction.InheritanceDemand,Unrestricted=true)] +#endif + [ComVisible(true)] +#if FEATURE_REMOTING + public abstract class FileSystemInfo : MarshalByRefObject, ISerializable { +#else // FEATURE_REMOTING + public abstract class FileSystemInfo : ISerializable { +#endif //FEATURE_REMOTING + + [System.Security.SecurityCritical] // auto-generated + internal Win32Native.WIN32_FILE_ATTRIBUTE_DATA _data; // Cache the file information + internal int _dataInitialised = -1; // We use this field in conjunction with the Refresh methods, if we succeed + // we store a zero, on failure we store the HResult in it so that we can + // give back a generic error back. + + private const int ERROR_INVALID_PARAMETER = 87; + internal const int ERROR_ACCESS_DENIED = 0x5; + + protected String FullPath; // fully qualified path of the directory + protected String OriginalPath; // path passed in by the user + private String _displayPath = ""; // path that can be displayed to the user + +#if FEATURE_CORECLR +#if FEATURE_CORESYSTEM + [System.Security.SecurityCritical] +#else + [System.Security.SecuritySafeCritical] +#endif //FEATURE_CORESYSTEM +#endif + protected FileSystemInfo() + { + } + + protected FileSystemInfo(SerializationInfo info, StreamingContext context) + { + if (info == null) + throw new ArgumentNullException("info"); + Contract.EndContractBlock(); + + // Must use V1 field names here, since V1 didn't implement + // ISerializable. + FullPath = Path.GetFullPathInternal(info.GetString("FullPath")); + OriginalPath = info.GetString("OriginalPath"); + + // Lazily initialize the file attributes. + _dataInitialised = -1; + } + + [System.Security.SecurityCritical] + internal void InitializeFrom(Win32Native.WIN32_FIND_DATA findData) + { + _data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + _data.PopulateFrom(findData); + _dataInitialised = 0; + } + + // Full path of the direcory/file + public virtual String FullName { + [System.Security.SecuritySafeCritical] + get + { + String demandDir; + if (this is DirectoryInfo) + demandDir = Directory.GetDemandDir(FullPath, true); + else + demandDir = FullPath; +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, demandDir); + sourceState.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandDir).Demand(); +#endif + return FullPath; + } + } + + internal virtual String UnsafeGetFullName + { + [System.Security.SecurityCritical] + get + { + String demandDir; + if (this is DirectoryInfo) + demandDir = Directory.GetDemandDir(FullPath, true); + else + demandDir = FullPath; +#if !FEATURE_CORECLR + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, demandDir).Demand(); +#endif + return FullPath; + } + } + + public String Extension + { + get + { + // GetFullPathInternal would have already stripped out the terminating "." if present. + int length = FullPath.Length; + for (int i = length; --i >= 0;) { + char ch = FullPath[i]; + if (ch == '.') + return FullPath.Substring(i, length - i); + if (ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar) + break; + } + return String.Empty; + } + } + + // For files name of the file is returned, for directories the last directory in hierarchy is returned if possible, + // otherwise the fully qualified name s returned + public abstract String Name { + get; + } + + // Whether a file/directory exists + public abstract bool Exists + { + get; + } + + // Delete a file/directory + public abstract void Delete(); + + public DateTime CreationTime + { + get { + // depends on the security check in get_CreationTimeUtc + return CreationTimeUtc.ToLocalTime(); + } + + set { + CreationTimeUtc = value.ToUniversalTime(); + } + } + + [ComVisible(false)] + public DateTime CreationTimeUtc { + [System.Security.SecuritySafeCritical] + get { +#if FEATURE_CORECLR + // get_CreationTime also depends on this security check + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, FullPath); + sourceState.EnsureState(); +#endif + if (_dataInitialised == -1) { + _data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + Refresh(); + } + + if (_dataInitialised != 0) // Refresh was unable to initialise the data + __Error.WinIOError(_dataInitialised, DisplayPath); + + long fileTime = ((long)_data.ftCreationTimeHigh << 32) | _data.ftCreationTimeLow; + return DateTime.FromFileTimeUtc(fileTime); + + } + + set { + if (this is DirectoryInfo) + Directory.SetCreationTimeUtc(FullPath,value); + else + File.SetCreationTimeUtc(FullPath,value); + _dataInitialised = -1; + } + } + + + public DateTime LastAccessTime + { + get { + // depends on the security check in get_LastAccessTimeUtc + return LastAccessTimeUtc.ToLocalTime(); + } + set { + LastAccessTimeUtc = value.ToUniversalTime(); + } + } + + [ComVisible(false)] + public DateTime LastAccessTimeUtc { + [System.Security.SecuritySafeCritical] + get { +#if FEATURE_CORECLR + // get_LastAccessTime also depends on this security check + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, FullPath); + sourceState.EnsureState(); +#endif + if (_dataInitialised == -1) { + _data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + Refresh(); + } + + if (_dataInitialised != 0) // Refresh was unable to initialise the data + __Error.WinIOError(_dataInitialised, DisplayPath); + + long fileTime = ((long)_data.ftLastAccessTimeHigh << 32) | _data.ftLastAccessTimeLow; + return DateTime.FromFileTimeUtc(fileTime); + + } + + set { + if (this is DirectoryInfo) + Directory.SetLastAccessTimeUtc(FullPath,value); + else + File.SetLastAccessTimeUtc(FullPath,value); + _dataInitialised = -1; + } + } + + public DateTime LastWriteTime + { + get { + // depends on the security check in get_LastWriteTimeUtc + return LastWriteTimeUtc.ToLocalTime(); + } + + set { + LastWriteTimeUtc = value.ToUniversalTime(); + } + } + + [ComVisible(false)] + public DateTime LastWriteTimeUtc { + [System.Security.SecuritySafeCritical] + get { +#if FEATURE_CORECLR + // get_LastWriteTime also depends on this security check + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, FullPath); + sourceState.EnsureState(); +#endif + if (_dataInitialised == -1) { + _data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + Refresh(); + } + + if (_dataInitialised != 0) // Refresh was unable to initialise the data + __Error.WinIOError(_dataInitialised, DisplayPath); + + + long fileTime = ((long)_data.ftLastWriteTimeHigh << 32) | _data.ftLastWriteTimeLow; + return DateTime.FromFileTimeUtc(fileTime); + } + + set { + if (this is DirectoryInfo) + Directory.SetLastWriteTimeUtc(FullPath,value); + else + File.SetLastWriteTimeUtc(FullPath,value); + _dataInitialised = -1; + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void Refresh() + { + _dataInitialised = File.FillAttributeInfo(FullPath, ref _data, false, false); + } + + public FileAttributes Attributes { + [System.Security.SecuritySafeCritical] + get + { +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.Read, String.Empty, FullPath); + sourceState.EnsureState(); +#endif + if (_dataInitialised == -1) { + _data = new Win32Native.WIN32_FILE_ATTRIBUTE_DATA(); + Refresh(); // Call refresh to intialise the data + } + + if (_dataInitialised != 0) // Refresh was unable to initialise the data + __Error.WinIOError(_dataInitialised, DisplayPath); + + return (FileAttributes) _data.fileAttributes; + } +#if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated +#else + [System.Security.SecuritySafeCritical] +#endif + set { +#if !FEATURE_CORECLR + new FileIOPermission(FileIOPermissionAccess.Write, FullPath).Demand(); +#endif + bool r = Win32Native.SetFileAttributes(FullPath, (int) value); + if (!r) { + int hr = Marshal.GetLastWin32Error(); + + if (hr==ERROR_INVALID_PARAMETER) + throw new ArgumentException(Environment.GetResourceString("Arg_InvalidFileAttrs")); + + // For whatever reason we are turning ERROR_ACCESS_DENIED into + // ArgumentException here (probably done for some 9x code path). + // We can't change this now but special casing the error message instead. + if (hr == ERROR_ACCESS_DENIED) + throw new ArgumentException(Environment.GetResourceString("UnauthorizedAccess_IODenied_NoPathName")); + __Error.WinIOError(hr, DisplayPath); + } + _dataInitialised = -1; + } + } + + [System.Security.SecurityCritical] // auto-generated_required + [ComVisible(false)] + public virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { +#if !FEATURE_CORECLR + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, FullPath).Demand(); +#endif + + info.AddValue("OriginalPath", OriginalPath, typeof(String)); + info.AddValue("FullPath", FullPath, typeof(String)); + } + + internal String DisplayPath + { + get + { + return _displayPath; + } + set + { + _displayPath = value; + } + } + } +} diff --git a/src/mscorlib/src/System/IO/IOException.cs b/src/mscorlib/src/System/IO/IOException.cs new file mode 100644 index 0000000000..b3db033b0d --- /dev/null +++ b/src/mscorlib/src/System/IO/IOException.cs @@ -0,0 +1,68 @@ +// 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: Exception for a generic IO error. +** +** +===========================================================*/ + +using System; +using System.Runtime.Serialization; + +namespace System.IO { + + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class IOException : SystemException + { + // For debugging purposes, store the complete path in the IOException + // if possible. Don't give it back to users due to security concerns. + // Let the code that throws the exception worry about that. But we can + // at least assist people attached to the process with a managed + // debugger. Don't serialize it to avoid any security problems. + // This information isn't guaranteed to be correct, but is our second + // best effort at a file or directory involved, after the exception + // message. + [NonSerialized] + private String _maybeFullPath; // For debuggers on partial trust code + + public IOException() + : base(Environment.GetResourceString("Arg_IOException")) { + SetErrorCode(__HResults.COR_E_IO); + } + + public IOException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_IO); + } + + public IOException(String message, int hresult) + : base(message) { + SetErrorCode(hresult); + } + + // Adding this for debuggers when looking at exceptions in partial + // trust code that may not have interesting path information in + // the exception message. + internal IOException(String message, int hresult, String maybeFullPath) + : base(message) { + SetErrorCode(hresult); + _maybeFullPath = maybeFullPath; + } + + public IOException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_IO); + } + + protected IOException(SerializationInfo info, StreamingContext context) : base (info, context) { + } + } +} diff --git a/src/mscorlib/src/System/IO/LongPathHelper.cs b/src/mscorlib/src/System/IO/LongPathHelper.cs new file mode 100644 index 0000000000..9746fdc0aa --- /dev/null +++ b/src/mscorlib/src/System/IO/LongPathHelper.cs @@ -0,0 +1,521 @@ +// 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.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Microsoft.Win32; + +namespace System.IO +{ + /// <summary> + /// Wrapper to help with path normalization. + /// </summary> + internal class LongPathHelper + { + // Can't be over 8.3 and be a short name + private const int MaxShortName = 12; + + private const char LastAnsi = (char)255; + private const char Delete = (char)127; + + [ThreadStatic] + private static StringBuffer t_fullPathBuffer; + + /// <summary> + /// Normalize the given path. + /// </summary> + /// <remarks> + /// Normalizes via Win32 GetFullPathName(). It will also trim all "typical" whitespace at the end of the path (see s_trimEndChars). Will also trim initial + /// spaces if the path is determined to be rooted. + /// + /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt) + /// </remarks> + /// <param name="path">Path to normalize</param> + /// <param name="checkInvalidCharacters">True to check for invalid characters</param> + /// <param name="expandShortPaths">Attempt to expand short paths if true</param> + /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception> + /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception> + /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception> + /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception> + /// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception> + /// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception> + /// <returns>Normalized path</returns> + [System.Security.SecurityCritical] + unsafe internal static string Normalize(string path, uint maxPathLength, bool checkInvalidCharacters, bool expandShortPaths) + { + // Get the full path + StringBuffer fullPath = t_fullPathBuffer ?? (t_fullPathBuffer = new StringBuffer(PathInternal.MaxShortPath)); + try + { + GetFullPathName(path, fullPath); + + // Trim whitespace off the end of the string. Win32 normalization trims only U+0020. + fullPath.TrimEnd(Path.TrimEndChars); + + if (fullPath.Length >= maxPathLength) + { + // Fullpath is genuinely too long + throw new PathTooLongException(); + } + + // Checking path validity used to happen before getting the full path name. To avoid additional input allocation + // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that + // used to get kicked back (notably segments with invalid characters might get removed via ".."). + // + // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to + // expand short file names. + + // Scan the path for: + // + // - Illegal path characters. + // - Invalid UNC paths like \\, \\server, \\server\. + // - Segments that are too long (over MaxComponentLength) + + // As the path could be > 60K, we'll combine the validity scan. None of these checks are performed by the Win32 + // GetFullPathName() API. + + bool possibleShortPath = false; + bool foundTilde = false; + + // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't + // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device + // path that contains UNC or to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\, + // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc. + bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\'; + bool isDevice = PathInternal.IsDevice(fullPath); + bool possibleBadUnc = specialPath && !isDevice; + uint index = specialPath ? 2u : 0; + uint lastSeparator = specialPath ? 1u : 0; + uint segmentLength; + char* start = fullPath.CharPointer; + char current; + + while (index < fullPath.Length) + { + current = start[index]; + + // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~' + if (current < '?' || current == '\\' || current == '|' || current == '~') + { + switch (current) + { + case '|': + case '>': + case '<': + case '\"': + if (checkInvalidCharacters) throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); + // No point in expanding a bad path + foundTilde = false; + break; + case '~': + foundTilde = true; + break; + case '\\': + segmentLength = index - lastSeparator - 1; + if (segmentLength > (uint)PathInternal.MaxComponentLength) + throw new PathTooLongException(); + lastSeparator = index; + + if (foundTilde) + { + if (segmentLength <= MaxShortName) + { + // Possibly a short path. + possibleShortPath = true; + } + + foundTilde = false; + } + + if (possibleBadUnc) + { + // If we're at the end of the path and this is the first separator, we're missing the share. + // Otherwise we're good, so ignore UNC tracking from here. + if (index == fullPath.Length - 1) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); + else + possibleBadUnc = false; + } + + break; + + default: + if (checkInvalidCharacters && current < ' ') throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); + break; + } + } + + index++; + } + + if (possibleBadUnc) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); + + segmentLength = fullPath.Length - lastSeparator - 1; + if (segmentLength > (uint)PathInternal.MaxComponentLength) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + if (foundTilde && segmentLength <= MaxShortName) + possibleShortPath = true; + + // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but + // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide. + if (expandShortPaths && possibleShortPath) + { + return TryExpandShortFileName(fullPath, originalPath: path); + } + else + { + if (fullPath.Length == (uint)path.Length && fullPath.StartsWith(path)) + { + // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder. + return path; + } + else + { + return fullPath.ToString(); + } + } + } + finally + { + // Clear the buffer + fullPath.Free(); + } + } + + [System.Security.SecurityCritical] + unsafe private static void GetFullPathName(string path, StringBuffer fullPath) + { + // If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as + // it doesn't root extended paths correctly. We don't currently resolve extended paths, so we'll just assert here. + Contract.Assert(PathInternal.IsPartiallyQualified(path) || !PathInternal.IsExtended(path)); + + // Historically we would skip leading spaces *only* if the path started with a drive " C:" or a UNC " \\" + int startIndex = PathInternal.PathStartSkip(path); + + fixed (char* pathStart = path) + { + uint result = 0; + while ((result = Win32Native.GetFullPathNameW(pathStart + startIndex, fullPath.CharCapacity, fullPath.GetHandle(), IntPtr.Zero)) > fullPath.CharCapacity) + { + // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. + fullPath.EnsureCharCapacity(result); + } + + if (result == 0) + { + // Failure, get the error and throw + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == 0) + errorCode = Win32Native.ERROR_BAD_PATHNAME; + __Error.WinIOError(errorCode, path); + } + + fullPath.Length = result; + } + } + + [System.Security.SecurityCritical] + unsafe internal static string GetLongPathName(StringBuffer path) + { + using (StringBuffer outputBuffer = new StringBuffer(path.Length)) + { + uint result = 0; + while ((result = Win32Native.GetLongPathNameW(path.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity) + { + // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. + outputBuffer.EnsureCharCapacity(result); + } + + if (result == 0) + { + // Failure, get the error and throw + GetErrorAndThrow(path.ToString()); + } + + outputBuffer.Length = result; + return outputBuffer.ToString(); + } + } + + [System.Security.SecurityCritical] + unsafe internal static string GetLongPathName(string path) + { + using (StringBuffer outputBuffer = new StringBuffer((uint)path.Length)) + { + uint result = 0; + while ((result = Win32Native.GetLongPathNameW(path, outputBuffer.GetHandle(), outputBuffer.CharCapacity)) > outputBuffer.CharCapacity) + { + // Reported size (which does not include the null) is greater than the buffer size. Increase the capacity. + outputBuffer.EnsureCharCapacity(result); + } + + if (result == 0) + { + // Failure, get the error and throw + GetErrorAndThrow(path); + } + + outputBuffer.Length = result; + return outputBuffer.ToString(); + } + } + + [System.Security.SecurityCritical] + private static void GetErrorAndThrow(string path) + { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == 0) + errorCode = Win32Native.ERROR_BAD_PATHNAME; + __Error.WinIOError(errorCode, path); + } + + // It is significantly more complicated to get the long path with minimal allocations if we're injecting the extended dos path prefix. The implicit version + // should match up with what is in CoreFx System.Runtime.Extensions. +#if !FEATURE_IMPLICIT_LONGPATH + [System.Security.SecuritySafeCritical] + private unsafe static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath) + { + // We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To + // avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls. + + Contract.Assert(!PathInternal.IsPartiallyQualified(outputBuffer), "should have resolved by now"); + + using (StringBuffer inputBuffer = new StringBuffer(outputBuffer)) + { + bool success = false; + uint lastIndex = outputBuffer.Length - 1; + uint foundIndex = lastIndex; + uint rootLength = PathInternal.GetRootLength(outputBuffer); + + while (!success) + { + uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); + + // Replace any temporary null we added + if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\'; + + if (result == 0) + { + // Look to see if we couldn't find the file + int error = Marshal.GetLastWin32Error(); + if (error != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.ERROR_PATH_NOT_FOUND) + { + // Some other failure, give up + break; + } + + // We couldn't find the path at the given index, start looking further back in the string. + foundIndex--; + + for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ; + if (foundIndex == rootLength) + { + // Can't trim the path back any further + break; + } + else + { + // Temporarily set a null in the string to get Windows to look further up the path + inputBuffer[foundIndex] = '\0'; + } + } + else if (result > outputBuffer.CharCapacity) + { + // Not enough space. The result count for this API does not include the null terminator. + outputBuffer.EnsureCharCapacity(result); + } + else + { + // Found the path + success = true; + outputBuffer.Length = result; + if (foundIndex < lastIndex) + { + // It was a partial find, put the non-existant part of the path back + outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex); + } + } + } + + StringBuffer bufferToUse = success ? outputBuffer : inputBuffer; + + if (bufferToUse.SubstringEquals(originalPath)) + { + // Use the original path to avoid allocating + return originalPath; + } + + return bufferToUse.ToString(); + } + } +#else // !FEATURE_IMPLICIT_LONGPATH + + private static uint GetInputBuffer(StringBuffer content, bool isDosUnc, out StringBuffer buffer) + { + uint length = content.Length; + + length += isDosUnc + ? (uint)PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength + : PathInternal.DevicePrefixLength; + + buffer = new StringBuffer(length); + + if (isDosUnc) + { + // Put the extended UNC prefix (\\?\UNC\) in front of the path + buffer.CopyFrom(bufferIndex: 0, source: PathInternal.UncExtendedPathPrefix); + + // Copy the source buffer over after the existing UNC prefix + content.CopyTo( + bufferIndex: PathInternal.UncPrefixLength, + destination: buffer, + destinationIndex: PathInternal.UncExtendedPrefixLength, + count: content.Length - PathInternal.UncPrefixLength); + + // Return the prefix difference + return (uint)PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength; + } + else + { + uint prefixSize = (uint)PathInternal.ExtendedPathPrefix.Length; + buffer.CopyFrom(bufferIndex: 0, source: PathInternal.ExtendedPathPrefix); + content.CopyTo(bufferIndex: 0, destination: buffer, destinationIndex: prefixSize, count: content.Length); + return prefixSize; + } + } + + [System.Security.SecuritySafeCritical] + private static string TryExpandShortFileName(StringBuffer outputBuffer, string originalPath) + { + // We'll have one of a few cases by now (the normalized path will have already: + // + // 1. Dos path (C:\) + // 2. Dos UNC (\\Server\Share) + // 3. Dos device path (\\.\C:\, \\?\C:\) + // + // We want to put the extended syntax on the front if it doesn't already have it, which may mean switching from \\.\. + + uint rootLength = PathInternal.GetRootLength(outputBuffer); + bool isDevice = PathInternal.IsDevice(outputBuffer); + + StringBuffer inputBuffer = null; + bool isDosUnc = false; + uint 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 \\.\) + // We will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is). + inputBuffer = new StringBuffer(); + inputBuffer.Append(outputBuffer); + + if (outputBuffer[2] == '.') + { + wasDotDevice = true; + inputBuffer[2] = '?'; + } + } + else + { + // \\Server\Share, but not \\.\ or \\?\. + // We need to know this to be able to push \\?\UNC\ on if required + isDosUnc = outputBuffer.Length > 1 && outputBuffer[0] == '\\' && outputBuffer[1] == '\\' && !PathInternal.IsDevice(outputBuffer); + rootDifference = GetInputBuffer(outputBuffer, isDosUnc, out inputBuffer); + } + + rootLength += rootDifference; + uint inputLength = inputBuffer.Length; + + bool success = false; + uint foundIndex = inputBuffer.Length - 1; + + while (!success) + { + uint result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); + + // Replace any temporary null we added + if (inputBuffer[foundIndex] == '\0') inputBuffer[foundIndex] = '\\'; + + if (result == 0) + { + // Look to see if we couldn't find the file + int error = Marshal.GetLastWin32Error(); + if (error != Win32Native.ERROR_FILE_NOT_FOUND && error != Win32Native.ERROR_PATH_NOT_FOUND) + { + // Some other failure, give up + break; + } + + // We couldn't find the path at the given index, start looking further back in the string. + foundIndex--; + + for (; foundIndex > rootLength && inputBuffer[foundIndex] != '\\'; foundIndex--) ; + if (foundIndex == rootLength) + { + // Can't trim the path back any further + break; + } + else + { + // Temporarily set a null in the string to get Windows to look further up the path + inputBuffer[foundIndex] = '\0'; + } + } + else if (result > outputBuffer.CharCapacity) + { + // Not enough space. The result count for this API does not include the null terminator. + outputBuffer.EnsureCharCapacity(result); + result = Win32Native.GetLongPathNameW(inputBuffer.GetHandle(), outputBuffer.GetHandle(), outputBuffer.CharCapacity); + } + else + { + // Found the path + success = true; + outputBuffer.Length = result; + if (foundIndex < inputLength - 1) + { + // It was a partial find, put the non-existent part of the path back + outputBuffer.Append(inputBuffer, foundIndex, inputBuffer.Length - foundIndex); + } + } + } + + // Strip out the prefix and return the string + StringBuffer bufferToUse = success ? outputBuffer : inputBuffer; + + // Switch back from \\?\ to \\.\ if necessary + if (wasDotDevice) + bufferToUse[2] = '.'; + + string returnValue = null; + + int newLength = (int)(bufferToUse.Length - rootDifference); + if (isDosUnc) + { + // Need to go from \\?\UNC\ to \\?\UN\\ + bufferToUse[PathInternal.UncExtendedPrefixLength - PathInternal.UncPrefixLength] = '\\'; + } + + // We now need to strip out any added characters at the front of the string + if (bufferToUse.SubstringEquals(originalPath, rootDifference, newLength)) + { + // Use the original path to avoid allocating + returnValue = originalPath; + } + else + { + returnValue = bufferToUse.Substring(rootDifference, newLength); + } + + inputBuffer.Dispose(); + return returnValue; + } +#endif // FEATURE_IMPLICIT_LONGPATH + } +}
\ No newline at end of file diff --git a/src/mscorlib/src/System/IO/MemoryStream.cs b/src/mscorlib/src/System/IO/MemoryStream.cs new file mode 100644 index 0000000000..edb583b9b5 --- /dev/null +++ b/src/mscorlib/src/System/IO/MemoryStream.cs @@ -0,0 +1,646 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: A Stream whose backing store is memory. Great +** for temporary storage without creating a temp file. Also +** lets users expose a byte[] as a stream. +** +** +===========================================================*/ + +using System; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Diagnostics.Contracts; +using System.Threading; +using System.Threading.Tasks; +using System.Security.Permissions; + +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. + [Serializable] + [ComVisible(true)] + public class MemoryStream : Stream + { + private byte[] _buffer; // Either allocated internally or externally. + private int _origin; // For user-provided arrays, start at this origin + private int _position; // read/write head. + [ContractPublicPropertyName("Length")] + 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 bool _exposable; // Whether the array can be returned to the user. + private bool _isOpen; // Is this stream open or closed? + + [NonSerialized] + private Task<int> _lastReadTask; // The last successful task returned from ReadAsync + + private const int MemStreamMaxLength = Int32.MaxValue; + + public MemoryStream() + : this(0) { + } + + public MemoryStream(int capacity) { + if (capacity < 0) { + throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_NegativeCapacity")); + } + Contract.EndContractBlock(); + + _buffer = capacity != 0 ? new byte[capacity] : EmptyArray<byte>.Value; + _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("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + Contract.EndContractBlock(); + _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("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + _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 { + [Pure] + get { return _isOpen; } + } + + public override bool CanSeek { + [Pure] + get { return _isOpen; } + } + + public override bool CanWrite { + [Pure] + get { return _writable; } + } + + private void EnsureWriteable() { + if (!CanWrite) __Error.WriteNotSupported(); + } + + 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(Environment.GetResourceString("IO.IO_StreamTooLong")); + if (value > _capacity) { + int newCapacity = value; + if (newCapacity < 256) + newCapacity = 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.MaxArrayLengthOneDimensional + // And we want to give the user the value that they asked for + if ((uint)(_capacity * 2) > Array.MaxByteArrayLength) + newCapacity = value > Array.MaxByteArrayLength ? value : Array.MaxByteArrayLength; + + Capacity = newCapacity; + return true; + } + return false; + } + + public override void Flush() { + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + 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(Environment.GetResourceString("UnauthorizedAccess_MemStreamBuffer")); + return _buffer; + } + + public virtual bool TryGetBuffer(out ArraySegment<byte> buffer) { + if (!_exposable) { + buffer = default(ArraySegment<byte>); + 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: Get origin and length - used in ResourceWriter. + [FriendAccessAllowed] + internal void InternalGetOriginAndLength(out int origin, out int length) + { + if (!_isOpen) __Error.StreamIsClosed(); + origin = _origin; + length = _length; + } + + // PERF: True cursor position, we don't need _origin for direct access + internal int InternalGetPosition() { + if (!_isOpen) __Error.StreamIsClosed(); + return _position; + } + + // PERF: Takes out Int32 as fast as possible + internal int InternalReadInt32() { + if (!_isOpen) + __Error.StreamIsClosed(); + + int pos = (_position += 4); // use temp to avoid a race condition + if (pos > _length) + { + _position = _length; + __Error.EndOfFile(); + } + return (int)(_buffer[pos-4] | _buffer[pos-3] << 8 | _buffer[pos-2] << 16 | _buffer[pos-1] << 24); + } + + // 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) { + if (!_isOpen) __Error.StreamIsClosed(); + + int n = _length - _position; + if (n > count) n = count; + if (n < 0) n = 0; + + Contract.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 { + if (!_isOpen) __Error.StreamIsClosed(); + 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("value", Environment.GetResourceString("ArgumentOutOfRange_SmallCapacity")); + Contract.Ensures(_capacity - _origin == value); + Contract.EndContractBlock(); + + if (!_isOpen) __Error.StreamIsClosed(); + if (!_expandable && (value != Capacity)) __Error.MemoryStreamNotExpandable(); + + // 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.InternalBlockCopy(_buffer, 0, newBuffer, 0, _length); + _buffer = newBuffer; + } + else { + _buffer = null; + } + _capacity = value; + } + } + } + + public override long Length { + get { + if (!_isOpen) __Error.StreamIsClosed(); + return _length - _origin; + } + } + + public override long Position { + get { + if (!_isOpen) __Error.StreamIsClosed(); + return _position - _origin; + } + set { + if (value < 0) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.Ensures(Position == value); + Contract.EndContractBlock(); + + if (!_isOpen) __Error.StreamIsClosed(); + + if (value > MemStreamMaxLength) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_StreamLength")); + _position = _origin + (int)value; + } + } + + public override int Read([In, Out] byte[] buffer, int offset, int count) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (!_isOpen) __Error.StreamIsClosed(); + + int n = _length - _position; + if (n > count) n = count; + if (n <= 0) + return 0; + + Contract.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.InternalBlockCopy(_buffer, _position, buffer, offset, n); + _position += n; + + return n; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // contract validation copied from Read(...) + + // If cancellation was requested, bail early + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<int>(cancellationToken); + + try + { + int n = Read(buffer, offset, count); + var t = _lastReadTask; + Contract.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.FromCancellation<int>(oce); + } + catch (Exception exception) + { + return Task.FromException<int>(exception); + } + } + + + public override int ReadByte() { + if (!_isOpen) __Error.StreamIsClosed(); + + if (_position >= _length) return -1; + + return _buffer[_position++]; + } + + + public override Task CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) { + + // This implementation offers beter performance compared to the base class version. + + // The parameter checks must be in sync with the base version: + if (destination == null) + throw new ArgumentNullException("destination"); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + + if (!CanRead && !CanWrite) + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_StreamClosed")); + + if (!destination.CanRead && !destination.CanWrite) + throw new ObjectDisposedException("destination", Environment.GetResourceString("ObjectDisposed_StreamClosed")); + + if (!CanRead) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnreadableStream")); + + if (!destination.CanWrite) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnwritableStream")); + + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Read() or Write() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read/Write) when we are not sure. + if (this.GetType() != typeof(MemoryStream)) + return base.CopyToAsync(destination, bufferSize, cancellationToken); + + // If cancelled - 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) + + Int32 pos = _position; + Int32 n = InternalEmulateRead(_length - _position); + + // If destination is not a memory stream, write there asynchronously: + MemoryStream memStrDest = destination as MemoryStream; + if (memStrDest == null) + 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 long Seek(long offset, SeekOrigin loc) { + if (!_isOpen) __Error.StreamIsClosed(); + + if (offset > MemStreamMaxLength) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_StreamLength")); + switch(loc) { + case SeekOrigin.Begin: { + int tempPosition = unchecked(_origin + (int)offset); + if (offset < 0 || tempPosition < _origin) + throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin")); + _position = tempPosition; + break; + } + case SeekOrigin.Current: { + int tempPosition = unchecked(_position + (int)offset); + if (unchecked(_position + offset) < _origin || tempPosition < _origin) + throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin")); + _position = tempPosition; + break; + } + case SeekOrigin.End: { + int tempPosition = unchecked(_length + (int)offset); + if ( unchecked(_length + offset) < _origin || tempPosition < _origin ) + throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin")); + _position = tempPosition; + break; + } + default: + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin")); + } + + Contract.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, Int32.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 (Int32.MaxValue). + // + public override void SetLength(long value) { + if (value < 0 || value > Int32.MaxValue) { + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_StreamLength")); + } + Contract.Ensures(_length - _origin == value); + Contract.EndContractBlock(); + EnsureWriteable(); + + // Origin wasn't publicly exposed above. + Contract.Assert(MemStreamMaxLength == Int32.MaxValue); // Check parameter validation logic in this method if this fails. + if (value > (Int32.MaxValue - _origin)) { + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("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() { + BCLDebug.Perf(_exposable, "MemoryStream::GetBuffer will let you avoid a copy."); + byte[] copy = new byte[_length - _origin]; + Buffer.InternalBlockCopy(_buffer, _origin, copy, 0, _length - _origin); + return copy; + } + + public override void Write(byte[] buffer, int offset, int count) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (!_isOpen) __Error.StreamIsClosed(); + EnsureWriteable(); + + int i = _position + count; + // Check for overflow + if (i < 0) + throw new IOException(Environment.GetResourceString("IO.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.InternalBlockCopy(buffer, offset, _buffer, _position, count); + _position = i; + + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // contract validation copied from Write(...) + + // 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.FromCancellation<VoidTaskResult>(oce); + } + catch (Exception exception) + { + return Task.FromException(exception); + } + } + + public override void WriteByte(byte value) { + if (!_isOpen) __Error.StreamIsClosed(); + 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("stream", Environment.GetResourceString("ArgumentNull_Stream")); + Contract.EndContractBlock(); + + if (!_isOpen) __Error.StreamIsClosed(); + stream.Write(_buffer, _origin, _length - _origin); + } + } +} diff --git a/src/mscorlib/src/System/IO/Path.cs b/src/mscorlib/src/System/IO/Path.cs new file mode 100644 index 0000000000..4f7993633b --- /dev/null +++ b/src/mscorlib/src/System/IO/Path.cs @@ -0,0 +1,1435 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: A collection of path manipulation methods. +** +** +===========================================================*/ + +using System; +using System.Security.Permissions; +using Win32Native = Microsoft.Win32.Win32Native; +using System.Text; +using System.Runtime.InteropServices; +using System.Security; +#if FEATURE_LEGACYSURFACE +using System.Security.Cryptography; +#endif +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; + +namespace System.IO { + // Provides methods for processing directory strings in an ideally + // cross-platform manner. Most of the methods don't do a complete + // full parsing (such as examining a UNC hostname), but they will + // handle most string operations. + [ComVisible(true)] + public static class Path + { + // Platform specific directory separator character. This is backslash + // ('\') on Windows and slash ('/') on Unix. + // +#if !PLATFORM_UNIX + public static readonly char DirectorySeparatorChar = '\\'; + internal const string DirectorySeparatorCharAsString = "\\"; +#else + public static readonly char DirectorySeparatorChar = '/'; + internal const string DirectorySeparatorCharAsString = "/"; +#endif // !PLATFORM_UNIX + + // Platform specific alternate directory separator character. + // There is only one directory separator char on Unix, + // so the same definition is used for both Unix and Windows. + public static readonly char AltDirectorySeparatorChar = '/'; + + // Platform specific volume separator character. This is colon (':') + // on Windows and MacOS, and slash ('/') on Unix. This is mostly + // useful for parsing paths like "c:\windows" or "MacVolume:System Folder". + // +#if !PLATFORM_UNIX + public static readonly char VolumeSeparatorChar = ':'; +#else + public static readonly char VolumeSeparatorChar = '/'; +#endif // !PLATFORM_UNIX + + // Platform specific invalid list of characters in a path. + // See the "Naming a File" MSDN conceptual docs for more details on + // what is valid in a file name (which is slightly different from what + // is legal in a path name). + // Note: This list is duplicated in CheckInvalidPathChars + [Obsolete("Please use GetInvalidPathChars or GetInvalidFileNameChars instead.")] +#if !PLATFORM_UNIX + public static readonly char[] InvalidPathChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31 }; +#else + public static readonly char[] InvalidPathChars = { '\0' }; +#endif // !PLATFORM_UNIX + + // Trim trailing white spaces, tabs etc but don't be aggressive in removing everything that has UnicodeCategory of trailing space. + // String.WhitespaceChars will trim aggressively than what the underlying FS does (for ex, NTFS, FAT). + internal static readonly char[] TrimEndChars = + { + (char)0x09, // Horizontal tab + (char)0x0A, // Line feed + (char)0x0B, // Vertical tab + (char)0x0C, // Form feed + (char)0x0D, // Carriage return + (char)0x20, // Space + (char)0x85, // Next line + (char)0xA0 // Non breaking space + }; + +#if !PLATFORM_UNIX + private static readonly char[] RealInvalidPathChars = PathInternal.InvalidPathChars; + + private static readonly char[] InvalidFileNameChars = { '\"', '<', '>', '|', '\0', (Char)1, (Char)2, (Char)3, (Char)4, (Char)5, (Char)6, (Char)7, (Char)8, (Char)9, (Char)10, (Char)11, (Char)12, (Char)13, (Char)14, (Char)15, (Char)16, (Char)17, (Char)18, (Char)19, (Char)20, (Char)21, (Char)22, (Char)23, (Char)24, (Char)25, (Char)26, (Char)27, (Char)28, (Char)29, (Char)30, (Char)31, ':', '*', '?', '\\', '/' }; +#else + private static readonly char[] RealInvalidPathChars = { '\0' }; + + private static readonly char[] InvalidFileNameChars = { '\0', '/' }; +#endif // !PLATFORM_UNIX + +#if !PLATFORM_UNIX + public static readonly char PathSeparator = ';'; +#else + public static readonly char PathSeparator = ':'; +#endif // !PLATFORM_UNIX + + + // The max total path is 260, and the max individual component length is 255. + // For example, D:\<256 char file name> isn't legal, even though it's under 260 chars. + internal static readonly int MaxPath = PathInternal.MaxShortPath; + + internal static readonly int MaxPathComponentLength = PathInternal.MaxComponentLength; + + // Windows API definitions + internal const int MAX_PATH = 260; // From WinDef.h + internal const int MAX_DIRECTORY_PATH = 248; // cannot create directories greater than 248 characters + + // Changes the extension of a file path. The path parameter + // specifies a file path, and the extension parameter + // specifies a file extension (with a leading period, such as + // ".exe" or ".cs"). + // + // The function returns a file path with the same root, directory, and base + // name parts as path, but with the file extension changed to + // the specified extension. If path is null, the function + // returns null. If path does not contain a file extension, + // the new file extension is appended to the path. If extension + // is null, any exsiting extension is removed from path. + // + public static String ChangeExtension(String path, String extension) { + if (path != null) { + CheckInvalidPathChars(path); + + String s = path; + for (int i = path.Length; --i >= 0;) { + char ch = path[i]; + if (ch == '.') { + s = path.Substring(0, i); + break; + } + if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break; + } + if (extension != null && path.Length != 0) { + if (extension.Length == 0 || extension[0] != '.') { + s = s + "."; + } + s = s + extension; + } + return s; + } + return null; + } + + // Returns the directory path of a file path. This method effectively + // removes the last element of the given file path, i.e. it returns a + // string consisting of all characters up to but not including the last + // backslash ("\") in the file path. The returned value is null if the file + // path is null or if the file path denotes a root (such as "\", "C:", or + // "\\server\share"). + public static String GetDirectoryName(String path) + { + return GetDirectoryNameInternal(path); + } + + [System.Security.SecuritySafeCritical] + private static string GetDirectoryNameInternal(string path) + { + if (path != null) + { + CheckInvalidPathChars(path); + + // Expanding short paths is dangerous in this case as the results will change with the current directory. + // + // Suppose you have a path called "PICTUR~1\Foo". Now suppose you have two folders on disk "C:\Mine\Pictures Of Me" + // and "C:\Yours\Pictures of You". If the current directory is neither you'll get back "PICTUR~1". If it is "C:\Mine" + // get back "Pictures Of Me". "C:\Yours" would give back "Pictures of You". + // + // Because of this and as it isn't documented that short paths are expanded we will not expand short names unless + // we're in legacy mode. + string normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: +#if FEATURE_PATHCOMPAT + AppContextSwitches.UseLegacyPathHandling +#else + false +#endif + ); + + // If there are no permissions for PathDiscovery to this path, we should NOT expand the short paths + // as this would leak information about paths to which the user would not have access to. + if (path.Length > 0 +#if FEATURE_CAS_POLICY + // Only do the extra logic if we're not in full trust + && !CodeAccessSecurityEngine.QuickCheckForAllDemands() +#endif + ) + { + try + { + // If we were passed in a path with \\?\ we need to remove it as FileIOPermission does not like it. + string tempPath = RemoveLongPathPrefix(path); + + // FileIOPermission cannot handle paths that contain ? or * + // So we only pass to FileIOPermission the text up to them. + int pos = 0; + while (pos < tempPath.Length && (tempPath[pos] != '?' && tempPath[pos] != '*')) + pos++; + + // GetFullPath will Demand that we have the PathDiscovery FileIOPermission and thus throw + // SecurityException if we don't. + // While we don't use the result of this call we are using it as a consistent way of + // doing the security checks. + if (pos > 0) + GetFullPath(tempPath.Substring(0, pos)); + } + catch (SecurityException) + { + // If the user did not have permissions to the path, make sure that we don't leak expanded short paths + // Only re-normalize if the original path had a ~ in it. + if (path.IndexOf("~", StringComparison.Ordinal) != -1) + { + normalizedPath = NormalizePath(path, fullCheck: false, expandShortPaths: false); + } + } + catch (PathTooLongException) { } + catch (NotSupportedException) { } // Security can throw this on "c:\foo:" + catch (IOException) { } + catch (ArgumentException) { } // The normalizePath with fullCheck will throw this for file: and http: + } + + path = normalizedPath; + + int root = GetRootLength(path); + int i = path.Length; + if (i > root) + { + i = path.Length; + if (i == root) return null; + while (i > root && path[--i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar); + return path.Substring(0, i); + } + } + return null; + } + + // Gets the length of the root DirectoryInfo or whatever DirectoryInfo markers + // are specified for the first part of the DirectoryInfo name. + // + internal static int GetRootLength(string path) + { + CheckInvalidPathChars(path); + +#if !PLATFORM_UNIX && FEATURE_PATHCOMPAT + if (AppContextSwitches.UseLegacyPathHandling) + { + int i = 0; + int length = path.Length; + + if (length >= 1 && (IsDirectorySeparator(path[0]))) + { + // handles UNC names and directories off current drive's root. + i = 1; + if (length >= 2 && (IsDirectorySeparator(path[1]))) + { + i = 2; + int n = 2; + while (i < length && ((path[i] != DirectorySeparatorChar && path[i] != AltDirectorySeparatorChar) || --n > 0)) i++; + } + } + else if (length >= 2 && path[1] == VolumeSeparatorChar) + { + // handles A:\foo. + i = 2; + if (length >= 3 && (IsDirectorySeparator(path[2]))) i++; + } + return i; + } + else +#endif // !PLATFORM_UNIX && FEATURE_PATHCOMPAT + { + return PathInternal.GetRootLength(path); + } + } + + internal static bool IsDirectorySeparator(char c) { + return (c==DirectorySeparatorChar || c == AltDirectorySeparatorChar); + } + + public static char[] GetInvalidPathChars() + { + return (char[]) RealInvalidPathChars.Clone(); + } + + public static char[] GetInvalidFileNameChars() + { + return (char[]) InvalidFileNameChars.Clone(); + } + + // Returns the extension of the given path. The returned value includes the + // period (".") character of the extension except when you have a terminal period when you get String.Empty, such as ".exe" or + // ".cpp". The returned value is null if the given path is + // null or if the given path does not include an extension. + // + [Pure] + public static String GetExtension(String path) { + if (path==null) + return null; + + CheckInvalidPathChars(path); + int length = path.Length; + for (int i = length; --i >= 0;) { + char ch = path[i]; + if (ch == '.') + { + if (i != length - 1) + return path.Substring(i, length - i); + else + return String.Empty; + } + if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) + break; + } + return String.Empty; + } + + // Expands the given path to a fully qualified path. The resulting string + // consists of a drive letter, a colon, and a root relative path. This + // function does not verify that the resulting path + // refers to an existing file or directory on the associated volume. + [Pure] + [System.Security.SecuritySafeCritical] + public static String GetFullPath(String path) { + String fullPath = GetFullPathInternal(path); +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, path, fullPath); + state.EnsureState(); +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false); +#endif + return fullPath; + } + + [System.Security.SecurityCritical] + internal static String UnsafeGetFullPath(String path) + { + String fullPath = GetFullPathInternal(path); +#if !FEATURE_CORECLR + FileIOPermission.QuickDemand(FileIOPermissionAccess.PathDiscovery, fullPath, false, false); +#endif + return fullPath; + } + + // This method is package access to let us quickly get a string name + // while avoiding a security check. This also serves a slightly + // different purpose - when we open a file, we need to resolve the + // path into a fully qualified, non-relative path name. This + // method does that, finding the current drive &; directory. But + // as long as we don't return this info to the user, we're good. However, + // the public GetFullPath does need to do a security check. + internal static string GetFullPathInternal(string path) + { + if (path == null) + throw new ArgumentNullException("path"); + Contract.EndContractBlock(); + + string newPath = NormalizePath(path, fullCheck: true); + return newPath; + } + + [System.Security.SecuritySafeCritical] // auto-generated + internal unsafe static string NormalizePath(string path, bool fullCheck) + { + return NormalizePath(path, fullCheck, +#if FEATURE_PATHCOMPAT + AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath : +#endif + PathInternal.MaxLongPath); + } + + [System.Security.SecuritySafeCritical] // auto-generated + internal unsafe static string NormalizePath(string path, bool fullCheck, bool expandShortPaths) + { + return NormalizePath(path, fullCheck, +#if FEATURE_PATHCOMPAT + AppContextSwitches.BlockLongPaths ? PathInternal.MaxShortPath : +#endif + PathInternal.MaxLongPath, + expandShortPaths); + } + + [System.Security.SecuritySafeCritical] // auto-generated + internal static string NormalizePath(string path, bool fullCheck, int maxPathLength) + { + return NormalizePath(path, fullCheck, maxPathLength, expandShortPaths: true); + } + + [System.Security.SecuritySafeCritical] + internal static string NormalizePath(string path, bool fullCheck, int maxPathLength, bool expandShortPaths) + { +#if FEATURE_PATHCOMPAT + if (AppContextSwitches.UseLegacyPathHandling) + { + return LegacyNormalizePath(path, fullCheck, maxPathLength, expandShortPaths); + } + else +#endif // FEATURE_APPCOMPAT + { + if (PathInternal.IsExtended(path)) + { + // We can't really know what is valid for all cases of extended paths. + // + // - object names can include other characters as well (':', '/', etc.) + // - even file objects have different rules (pipe names can contain most characters) + // + // As such we will do no further analysis of extended paths to avoid blocking known and unknown + // scenarios as well as minimizing compat breaks should we block now and need to unblock later. + return path; + } + + string normalizedPath = null; + + if (fullCheck == false) + { + // Disabled fullCheck is only called by GetDirectoryName and GetPathRoot. + // Avoid adding addtional callers and try going direct to lighter weight NormalizeDirectorySeparators. + normalizedPath = NewNormalizePathLimitedChecks(path, maxPathLength, expandShortPaths); + } + else + { + normalizedPath = NewNormalizePath(path, maxPathLength, expandShortPaths: true); + } + + if (string.IsNullOrWhiteSpace(normalizedPath)) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + return normalizedPath; + } + } + + [System.Security.SecuritySafeCritical] + private static string NewNormalizePathLimitedChecks(string path, int maxPathLength, bool expandShortPaths) + { + string normalized = PathInternal.NormalizeDirectorySeparators(path); + + if (PathInternal.IsPathTooLong(normalized) || PathInternal.AreSegmentsTooLong(normalized)) + throw new PathTooLongException(); + +#if !PLATFORM_UNIX + if (!PathInternal.IsDevice(normalized) && PathInternal.HasInvalidVolumeSeparator(path)) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + if (expandShortPaths && normalized.IndexOf('~') != -1) + { + try + { + return LongPathHelper.GetLongPathName(normalized); + } + catch + { + // Don't care if we can't get the long path- might not exist, etc. + } + } +#endif + + return normalized; + } + + /// <summary> + /// Normalize the path and check for bad characters or other invalid syntax. + /// </summary> + [System.Security.SecuritySafeCritical] + [ResourceExposure(ResourceScope.Machine)] + [ResourceConsumption(ResourceScope.Machine)] + private static string NewNormalizePath(string path, int maxPathLength, bool expandShortPaths) + { + Contract.Requires(path != null, "path can't be null"); + + // Embedded null characters are the only invalid character case we want to check up front. + // This is because the nulls will signal the end of the string to Win32 and therefore have + // unpredictable results. Other invalid characters we give a chance to be normalized out. + if (path.IndexOf('\0') != -1) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); + +#if !PLATFORM_UNIX + // Note that colon and wildcard checks happen in FileIOPermissions + + // Technically this doesn't matter but we used to throw for this case + if (string.IsNullOrWhiteSpace(path)) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + // We don't want to check invalid characters for device format- see comments for extended above + return LongPathHelper.Normalize(path, (uint)maxPathLength, checkInvalidCharacters: !PathInternal.IsDevice(path), expandShortPaths: expandShortPaths); +#else + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + // Expand with current directory if necessary + if (!IsPathRooted(path)) + path = Combine(Directory.GetCurrentDirectory(), path); + + // We would ideally use realpath to do this, but it resolves symlinks, requires that the file actually exist, + // and turns it into a full path, which we only want if fullCheck is true. + string collapsedString = PathInternal.RemoveRelativeSegments(path); + + if (collapsedString.Length > maxPathLength) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + return collapsedString.Length == 0 ? "/" : collapsedString; +#endif // PLATFORM_UNIX + } + +#if FEATURE_PATHCOMPAT + [System.Security.SecurityCritical] // auto-generated + internal unsafe static String LegacyNormalizePath(String path, bool fullCheck, int maxPathLength, bool expandShortPaths) { + + Contract.Requires(path != null, "path can't be null"); + // If we're doing a full path check, trim whitespace and look for + // illegal path characters. + if (fullCheck) { + // Trim whitespace off the end of the string. + // Win32 normalization trims only U+0020. + path = path.TrimEnd(TrimEndChars); + + // Look for illegal path characters. + if (PathInternal.AnyPathHasIllegalCharacters(path)) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); + } + + int index = 0; + // We prefer to allocate on the stack for workingset/perf gain. If the + // starting path is less than MaxPath then we can stackalloc; otherwise we'll + // use a StringBuilder (PathHelper does this under the hood). The latter may + // happen in 2 cases: + // 1. Starting path is greater than MaxPath but it normalizes down to MaxPath. + // This is relevant for paths containing escape sequences. In this case, we + // attempt to normalize down to MaxPath, but the caller pays a perf penalty + // since StringBuilder is used. + // 2. IsolatedStorage, which supports paths longer than MaxPath (value given + // by maxPathLength. + PathHelper newBuffer; + if (path.Length + 1 <= MaxPath) { + char* m_arrayPtr = stackalloc char[MaxPath]; + newBuffer = new PathHelper(m_arrayPtr, MaxPath); + } else { + newBuffer = new PathHelper(path.Length + Path.MaxPath, maxPathLength); + } + + uint numSpaces = 0; + uint numDots = 0; + bool fixupDirectorySeparator = false; + // Number of significant chars other than potentially suppressible + // dots and spaces since the last directory or volume separator char + uint numSigChars = 0; + int lastSigChar = -1; // Index of last significant character. + // Whether this segment of the path (not the complete path) started + // with a volume separator char. Reject "c:...". + bool startedWithVolumeSeparator = false; + bool firstSegment = true; + int lastDirectorySeparatorPos = 0; + +#if !PLATFORM_UNIX + bool mightBeShortFileName = false; + + // LEGACY: This code is here for backwards compatibility reasons. It + // ensures that \\foo.cs\bar.cs stays \\foo.cs\bar.cs instead of being + // turned into \foo.cs\bar.cs. + if (path.Length > 0 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) { + newBuffer.Append('\\'); + index++; + lastSigChar = 0; + } +#endif + + // Normalize the string, stripping out redundant dots, spaces, and + // slashes. + while (index < path.Length) { + char currentChar = path[index]; + + // We handle both directory separators and dots specially. For + // directory separators, we consume consecutive appearances. + // For dots, we consume all dots beyond the second in + // succession. All other characters are added as is. In + // addition we consume all spaces after the last other char + // in a directory name up until the directory separator. + + if (currentChar == DirectorySeparatorChar || currentChar == AltDirectorySeparatorChar) { + // If we have a path like "123.../foo", remove the trailing dots. + // However, if we found "c:\temp\..\bar" or "c:\temp\...\bar", don't. + // Also remove trailing spaces from both files & directory names. + // This was agreed on with the OS team to fix undeletable directory + // names ending in spaces. + + // If we saw a '\' as the previous last significant character and + // are simply going to write out dots, suppress them. + // If we only contain dots and slashes though, only allow + // a string like [dot]+ [space]*. Ignore everything else. + // Legal: "\.. \", "\...\", "\. \" + // Illegal: "\.. .\", "\. .\", "\ .\" + if (numSigChars == 0) { + // Dot and space handling + if (numDots > 0) { + // Look for ".[space]*" or "..[space]*" + int start = lastSigChar + 1; + if (path[start] != '.') + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + // Only allow "[dot]+[space]*", and normalize the + // legal ones to "." or ".." + if (numDots >= 2) { + // Reject "C:..." + if (startedWithVolumeSeparator && numDots > 2) + + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + if (path[start + 1] == '.') { + // Search for a space in the middle of the + // dots and throw + for(int i=start + 2; i < start + numDots; i++) { + if (path[i] != '.') + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + } + + numDots = 2; + } + else { + if (numDots > 1) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + numDots = 1; + } + } + + if (numDots == 2) { + newBuffer.Append('.'); + } + + newBuffer.Append('.'); + fixupDirectorySeparator = false; + + // Continue in this case, potentially writing out '\'. + } + + if (numSpaces > 0 && firstSegment) { + // Handle strings like " \\server\share". + if (index + 1 < path.Length && + (path[index + 1] == DirectorySeparatorChar || path[index + 1] == AltDirectorySeparatorChar)) + { + newBuffer.Append(DirectorySeparatorChar); + } + } + } + numDots = 0; + numSpaces = 0; // Suppress trailing spaces + + if (!fixupDirectorySeparator) { + fixupDirectorySeparator = true; + newBuffer.Append(DirectorySeparatorChar); + } + numSigChars = 0; + lastSigChar = index; + startedWithVolumeSeparator = false; + firstSegment = false; + +#if !PLATFORM_UNIX + // For short file names, we must try to expand each of them as + // soon as possible. We need to allow people to specify a file + // name that doesn't exist using a path with short file names + // in it, such as this for a temp file we're trying to create: + // C:\DOCUME~1\USERNA~1.RED\LOCALS~1\Temp\bg3ylpzp + // We could try doing this afterwards piece by piece, but it's + // probably a lot simpler to do it here. + if (mightBeShortFileName) { + newBuffer.TryExpandShortFileName(); + mightBeShortFileName = false; + } +#endif + int thisPos = newBuffer.Length - 1; + if (thisPos - lastDirectorySeparatorPos > MaxPathComponentLength) + { + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + } + lastDirectorySeparatorPos = thisPos; + } // if (Found directory separator) + else if (currentChar == '.') { + // Reduce only multiple .'s only after slash to 2 dots. For + // instance a...b is a valid file name. + numDots++; + // Don't flush out non-terminal spaces here, because they may in + // the end not be significant. Turn "c:\ . .\foo" -> "c:\foo" + // which is the conclusion of removing trailing dots & spaces, + // as well as folding multiple '\' characters. + } + else if (currentChar == ' ') { + numSpaces++; + } + else { // Normal character logic +#if !PLATFORM_UNIX + if (currentChar == '~' && expandShortPaths) + mightBeShortFileName = true; +#endif + + fixupDirectorySeparator = false; + +#if !PLATFORM_UNIX + // To reject strings like "C:...\foo" and "C :\foo" + if (firstSegment && currentChar == VolumeSeparatorChar) { + // Only accept "C:", not "c :" or ":" + // Get a drive letter or ' ' if index is 0. + char driveLetter = (index > 0) ? path[index-1] : ' '; + bool validPath = ((numDots == 0) && (numSigChars >= 1) && (driveLetter != ' ')); + if (!validPath) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + startedWithVolumeSeparator = true; + // We need special logic to make " c:" work, we should not fix paths like " foo::$DATA" + if (numSigChars > 1) { // Common case, simply do nothing + int spaceCount = 0; // How many spaces did we write out, numSpaces has already been reset. + while((spaceCount < newBuffer.Length) && newBuffer[spaceCount] == ' ') + spaceCount++; + if (numSigChars - spaceCount == 1) { + //Safe to update stack ptr directly + newBuffer.Length = 0; + newBuffer.Append(driveLetter); // Overwrite spaces, we need a special case to not break " foo" as a relative path. + } + } + numSigChars = 0; + } + else +#endif // !PLATFORM_UNIX + { + numSigChars += 1 + numDots + numSpaces; + } + + // Copy any spaces & dots since the last significant character + // to here. Note we only counted the number of dots & spaces, + // and don't know what order they're in. Hence the copy. + if (numDots > 0 || numSpaces > 0) { + int numCharsToCopy = (lastSigChar >= 0) ? index - lastSigChar - 1 : index; + if (numCharsToCopy > 0) { + for (int i=0; i<numCharsToCopy; i++) { + newBuffer.Append(path[lastSigChar + 1 + i]); + } + } + numDots = 0; + numSpaces = 0; + } + + newBuffer.Append(currentChar); + lastSigChar = index; + } + + index++; + } // end while + + if (newBuffer.Length - 1 - lastDirectorySeparatorPos > MaxPathComponentLength) + { + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + } + + // Drop any trailing dots and spaces from file & directory names, EXCEPT + // we MUST make sure that "C:\foo\.." is correctly handled. + // Also handle "C:\foo\." -> "C:\foo", while "C:\." -> "C:\" + if (numSigChars == 0) { + if (numDots > 0) { + // Look for ".[space]*" or "..[space]*" + int start = lastSigChar + 1; + if (path[start] != '.') + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + // Only allow "[dot]+[space]*", and normalize the + // legal ones to "." or ".." + if (numDots >= 2) { + // Reject "C:..." + if (startedWithVolumeSeparator && numDots > 2) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + if (path[start + 1] == '.') { + // Search for a space in the middle of the + // dots and throw + for(int i=start + 2; i < start + numDots; i++) { + if (path[i] != '.') + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + } + + numDots = 2; + } + else { + if (numDots > 1) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + numDots = 1; + } + } + + if (numDots == 2) { + newBuffer.Append('.'); + } + + newBuffer.Append('.'); + } + } // if (numSigChars == 0) + + // If we ended up eating all the characters, bail out. + if (newBuffer.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegal")); + + // Disallow URL's here. Some of our other Win32 API calls will reject + // them later, so we might be better off rejecting them here. + // Note we've probably turned them into "file:\D:\foo.tmp" by now. + // But for compatibility, ensure that callers that aren't doing a + // full check aren't rejected here. + if (fullCheck) { + if ( newBuffer.OrdinalStartsWith("http:", false) || + newBuffer.OrdinalStartsWith("file:", false)) + { + throw new ArgumentException(Environment.GetResourceString("Argument_PathUriFormatNotSupported")); + } + } + +#if !PLATFORM_UNIX + // If the last part of the path (file or directory name) had a tilde, + // expand that too. + if (mightBeShortFileName) { + newBuffer.TryExpandShortFileName(); + } +#endif + + // Call the Win32 API to do the final canonicalization step. + int result = 1; + + if (fullCheck) { + // NOTE: Win32 GetFullPathName requires the input buffer to be big enough to fit the initial + // path which is a concat of CWD and the relative path, this can be of an arbitrary + // size and could be > MAX_PATH (which becomes an artificial limit at this point), + // even though the final normalized path after fixing up the relative path syntax + // might be well within the MAX_PATH restriction. For ex, + // "c:\SomeReallyLongDirName(thinkGreaterThan_MAXPATH)\..\foo.txt" which actually requires a + // buffer well with in the MAX_PATH as the normalized path is just "c:\foo.txt" + // This buffer requirement seems wrong, it could be a bug or a perf optimization + // like returning required buffer length quickly or avoid stratch buffer etc. + // Ideally we would get the required buffer length first by calling GetFullPathName + // once without the buffer and use that in the later call but this doesn't always work + // due to Win32 GetFullPathName bug. For instance, in Win2k, when the path we are trying to + // fully qualify is a single letter name (such as "a", "1", ",") GetFullPathName + // fails to return the right buffer size (i.e, resulting in insufficient buffer). + // To workaround this bug we will start with MAX_PATH buffer and grow it once if the + // return value is > MAX_PATH. + + result = newBuffer.GetFullPathName(); + +#if !PLATFORM_UNIX + // If we called GetFullPathName with something like "foo" and our + // command window was in short file name mode (ie, by running edlin or + // DOS versions of grep, etc), we might have gotten back a short file + // name. So, check to see if we need to expand it. + mightBeShortFileName = false; + for(int i=0; i < newBuffer.Length && !mightBeShortFileName; i++) { + if (newBuffer[i] == '~' && expandShortPaths) + mightBeShortFileName = true; + } + + if (mightBeShortFileName) { + bool r = newBuffer.TryExpandShortFileName(); + // Consider how the path "Doesn'tExist" would expand. If + // we add in the current directory, it too will need to be + // fully expanded, which doesn't happen if we use a file + // name that doesn't exist. + if (!r) { + int lastSlash = -1; + + for (int i = newBuffer.Length - 1; i >= 0; i--) { + if (newBuffer[i] == DirectorySeparatorChar) { + lastSlash = i; + break; + } + } + + if (lastSlash >= 0) { + + // This bounds check is for safe memcpy but we should never get this far + if (newBuffer.Length >= maxPathLength) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + int lenSavedName = newBuffer.Length - lastSlash - 1; + Contract.Assert(lastSlash < newBuffer.Length, "path unexpectedly ended in a '\'"); + + newBuffer.Fixup(lenSavedName, lastSlash); + } + } + } +#endif // PLATFORM_UNIX + } + + if (result != 0) { + /* Throw an ArgumentException for paths like \\, \\server, \\server\ + This check can only be properly done after normalizing, so + \\foo\.. will be properly rejected. Also, reject \\?\GLOBALROOT\ + (an internal kernel path) because it provides aliases for drives. */ + if (newBuffer.Length > 1 && newBuffer[0] == '\\' && newBuffer[1] == '\\') { + int startIndex = 2; + while (startIndex < result) { + if (newBuffer[startIndex] == '\\') { + startIndex++; + break; + } + else { + startIndex++; + } + } + if (startIndex == result) + throw new ArgumentException(Environment.GetResourceString("Arg_PathIllegalUNC")); + + // Check for \\?\Globalroot, an internal mechanism to the kernel + // that provides aliases for drives and other undocumented stuff. + // The kernel team won't even describe the full set of what + // is available here - we don't want managed apps mucking + // with this for security reasons. + if ( newBuffer.OrdinalStartsWith("\\\\?\\globalroot", true)) + throw new ArgumentException(Environment.GetResourceString("Arg_PathGlobalRoot")); + } + } + + // Check our result and form the managed string as necessary. + if (newBuffer.Length >= maxPathLength) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + if (result == 0) { + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode == 0) + errorCode = Win32Native.ERROR_BAD_PATHNAME; + __Error.WinIOError(errorCode, path); + return null; // Unreachable - silence a compiler error. + } + + return newBuffer.ToStringOrExisting(path); + } +#endif // FEATURE_PATHCOMPAT + + internal const int MaxLongPath = PathInternal.MaxLongPath; + + private const string LongPathPrefix = PathInternal.ExtendedPathPrefix; + private const string UNCPathPrefix = PathInternal.UncPathPrefix; + private const string UNCLongPathPrefixToInsert = PathInternal.UncExtendedPrefixToInsert; + private const string UNCLongPathPrefix = PathInternal.UncExtendedPathPrefix; + + internal static bool HasLongPathPrefix(string path) + { +#if FEATURE_PATHCOMPAT + if (AppContextSwitches.UseLegacyPathHandling) + return path.StartsWith(LongPathPrefix, StringComparison.Ordinal); + else +#endif + return PathInternal.IsExtended(path); + } + + internal static string AddLongPathPrefix(string path) + { +#if FEATURE_PATHCOMPAT + if (AppContextSwitches.UseLegacyPathHandling) + { + if (path.StartsWith(LongPathPrefix, StringComparison.Ordinal)) + return path; + + if (path.StartsWith(UNCPathPrefix, StringComparison.Ordinal)) + return path.Insert(2, UNCLongPathPrefixToInsert); // Given \\server\share in longpath becomes \\?\UNC\server\share => UNCLongPathPrefix + path.SubString(2); => The actual command simply reduces the operation cost. + + return LongPathPrefix + path; + } + else +#endif + { + return PathInternal.EnsureExtendedPrefix(path); + } + } + + internal static string RemoveLongPathPrefix(string path) + { +#if FEATURE_PATHCOMPAT + if (AppContextSwitches.UseLegacyPathHandling) + { + if (!path.StartsWith(LongPathPrefix, StringComparison.Ordinal)) + return path; + + if (path.StartsWith(UNCLongPathPrefix, StringComparison.OrdinalIgnoreCase)) + return path.Remove(2, 6); // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost. + + return path.Substring(4); + } + else +#endif + { + return PathInternal.RemoveExtendedPrefix(path); + } + } + + internal static StringBuilder RemoveLongPathPrefix(StringBuilder pathSB) + { +#if FEATURE_PATHCOMPAT + if (AppContextSwitches.UseLegacyPathHandling) + { + if (!PathInternal.StartsWithOrdinal(pathSB, LongPathPrefix)) + return pathSB; + + // Given \\?\UNC\server\share we return \\server\share => @'\\' + path.SubString(UNCLongPathPrefix.Length) => The actual command simply reduces the operation cost. + if (PathInternal.StartsWithOrdinal(pathSB, UNCLongPathPrefix, ignoreCase: true)) + return pathSB.Remove(2, 6); + + return pathSB.Remove(0, 4); + } + else +#endif + { + return PathInternal.RemoveExtendedPrefix(pathSB); + } + } + + + // Returns the name and extension parts of the given path. The resulting + // string contains the characters of path that follow the last + // backslash ("\"), slash ("/"), or colon (":") character in + // path. The resulting string is the entire path if path + // contains no backslash after removing trailing slashes, slash, or colon characters. The resulting + // string is null if path is null. + // + [Pure] + public static String GetFileName(String path) { + if (path != null) { + CheckInvalidPathChars(path); + + int length = path.Length; + for (int i = length; --i >= 0;) { + char ch = path[i]; + if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) + return path.Substring(i + 1, length - i - 1); + + } + } + return path; + } + + [Pure] + public static String GetFileNameWithoutExtension(String path) { + path = GetFileName(path); + if (path != null) + { + int i; + if ((i=path.LastIndexOf('.')) == -1) + return path; // No path extension found + else + return path.Substring(0,i); + } + return null; + } + + + + // Returns the root portion of the given path. The resulting string + // consists of those rightmost characters of the path that constitute the + // root of the path. Possible patterns for the resulting string are: An + // empty string (a relative path on the current drive), "\" (an absolute + // path on the current drive), "X:" (a relative path on a given drive, + // where X is the drive letter), "X:\" (an absolute path on a given drive), + // and "\\server\share" (a UNC path for a given server and share name). + // The resulting string is null if path is null. + // + [Pure] + public static String GetPathRoot(String path) { + if (path == null) return null; + + // Expanding short paths has no impact on the path root- there is no such thing as an + // 8.3 volume or server/share name. + path = NormalizePath(path, fullCheck: false, expandShortPaths: false); + return path.Substring(0, GetRootLength(path)); + } + + [System.Security.SecuritySafeCritical] + public static String GetTempPath() + { +#if !FEATURE_CORECLR + new EnvironmentPermission(PermissionState.Unrestricted).Demand(); +#endif + StringBuilder sb = new StringBuilder(PathInternal.MaxShortPath); + uint r = Win32Native.GetTempPath(PathInternal.MaxShortPath, sb); + String path = sb.ToString(); + if (r==0) __Error.WinIOError(); + path = GetFullPathInternal(path); +#if FEATURE_CORECLR + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path); + state.EnsureState(); +#endif + return path; + } + + internal static bool IsRelative(string path) + { + Contract.Assert(path != null, "path can't be null"); + return PathInternal.IsPartiallyQualified(path); + } + + // Returns a cryptographically strong random 8.3 string that can be + // used as either a folder name or a file name. +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] +#endif + public static String GetRandomFileName() + { + // 5 bytes == 40 bits == 40/5 == 8 chars in our encoding + // This gives us exactly 8 chars. We want to avoid the 8.3 short name issue + byte[] key = new byte[10]; + +#if FEATURE_CORECLR + Win32Native.Random(true, key, key.Length); +#else + // RNGCryptoServiceProvider is disposable in post-Orcas desktop mscorlibs, but not in CoreCLR's + // mscorlib, so we need to do a manual using block for it. + RNGCryptoServiceProvider rng = null; + try + { + rng = new RNGCryptoServiceProvider(); + + rng.GetBytes(key); + } + finally + { + if (rng != null) + { + rng.Dispose(); + } + } +#endif + + // rndCharArray is expected to be 16 chars + char[] rndCharArray = Path.ToBase32StringSuitableForDirName(key).ToCharArray(); + rndCharArray[8] = '.'; + return new String(rndCharArray, 0, 12); + } + + // Returns a unique temporary file name, and creates a 0-byte file by that + // name on disk. + [System.Security.SecuritySafeCritical] + public static String GetTempFileName() + { + return InternalGetTempFileName(true); + } + + [System.Security.SecurityCritical] + internal static String UnsafeGetTempFileName() + { + return InternalGetTempFileName(false); + } + + [System.Security.SecurityCritical] + private static String InternalGetTempFileName(bool checkHost) + { + String path = GetTempPath(); + + // Since this can write to the temp directory and theoretically + // cause a denial of service attack, demand FileIOPermission to + // that directory. + +#if FEATURE_CORECLR + if (checkHost) + { + FileSecurityState state = new FileSecurityState(FileSecurityStateAccess.Write, String.Empty, path); + state.EnsureState(); + } +#else + FileIOPermission.QuickDemand(FileIOPermissionAccess.Write, path); +#endif + StringBuilder sb = new StringBuilder(MaxPath); + uint r = Win32Native.GetTempFileName(path, "tmp", 0, sb); + if (r==0) __Error.WinIOError(); + return sb.ToString(); + } + + // Tests if a path includes a file extension. The result is + // true if the characters that follow the last directory + // separator ('\\' or '/') or volume separator (':') in the path include + // a period (".") other than a terminal period. The result is false otherwise. + // + [Pure] + public static bool HasExtension(String path) { + if (path != null) { + CheckInvalidPathChars(path); + + for (int i = path.Length; --i >= 0;) { + char ch = path[i]; + if (ch == '.') { + if ( i != path.Length - 1) + return true; + else + return false; + } + if (ch == DirectorySeparatorChar || ch == AltDirectorySeparatorChar || ch == VolumeSeparatorChar) break; + } + } + return false; + } + + // Tests if the given path contains a root. A path is considered rooted + // if it starts with a backslash ("\") or a drive letter and a colon (":"). + // + [Pure] + public static bool IsPathRooted(String path) { + if (path != null) { + CheckInvalidPathChars(path); + + int length = path.Length; +#if !PLATFORM_UNIX + if ((length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) || (length >= 2 && path[1] == VolumeSeparatorChar)) + return true; +#else + if (length >= 1 && (path[0] == DirectorySeparatorChar || path[0] == AltDirectorySeparatorChar)) + return true; +#endif + } + return false; + } + + public static String Combine(String path1, String path2) { + if (path1==null || path2==null) + throw new ArgumentNullException((path1==null) ? "path1" : "path2"); + Contract.EndContractBlock(); + CheckInvalidPathChars(path1); + CheckInvalidPathChars(path2); + + return CombineNoChecks(path1, path2); + } + + public static String Combine(String path1, String path2, String path3) { + if (path1 == null || path2 == null || path3 == null) + throw new ArgumentNullException((path1 == null) ? "path1" : (path2 == null) ? "path2" : "path3"); + Contract.EndContractBlock(); + CheckInvalidPathChars(path1); + CheckInvalidPathChars(path2); + CheckInvalidPathChars(path3); + + return CombineNoChecks(CombineNoChecks(path1, path2), path3); + } + + public static String Combine(String path1, String path2, String path3, String path4) { + if (path1 == null || path2 == null || path3 == null || path4 == null) + throw new ArgumentNullException((path1 == null) ? "path1" : (path2 == null) ? "path2" : (path3 == null) ? "path3" : "path4"); + Contract.EndContractBlock(); + CheckInvalidPathChars(path1); + CheckInvalidPathChars(path2); + CheckInvalidPathChars(path3); + CheckInvalidPathChars(path4); + + return CombineNoChecks(CombineNoChecks(CombineNoChecks(path1, path2), path3), path4); + } + + public static String Combine(params String[] paths) { + if (paths == null) { + throw new ArgumentNullException("paths"); + } + Contract.EndContractBlock(); + + int finalSize = 0; + int firstComponent = 0; + + // We have two passes, the first calcuates how large a buffer to allocate and does some precondition + // checks on the paths passed in. The second actually does the combination. + + for (int i = 0; i < paths.Length; i++) { + if (paths[i] == null) { + throw new ArgumentNullException("paths"); + } + + if (paths[i].Length == 0) { + continue; + } + + CheckInvalidPathChars(paths[i]); + + if (Path.IsPathRooted(paths[i])) { + firstComponent = i; + finalSize = paths[i].Length; + } else { + finalSize += paths[i].Length; + } + + char ch = paths[i][paths[i].Length - 1]; + if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) + finalSize++; + } + + StringBuilder finalPath = StringBuilderCache.Acquire(finalSize); + + for (int i = firstComponent; i < paths.Length; i++) { + if (paths[i].Length == 0) { + continue; + } + + if (finalPath.Length == 0) { + finalPath.Append(paths[i]); + } else { + char ch = finalPath[finalPath.Length - 1]; + if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) { + finalPath.Append(DirectorySeparatorChar); + } + + finalPath.Append(paths[i]); + } + } + + return StringBuilderCache.GetStringAndRelease(finalPath); + } + + private static String CombineNoChecks(String path1, String path2) { + if (path2.Length == 0) + return path1; + + if (path1.Length == 0) + return path2; + + if (IsPathRooted(path2)) + return path2; + + char ch = path1[path1.Length - 1]; + if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) + return path1 + DirectorySeparatorCharAsString + path2; + return path1 + path2; + } + + private static readonly Char[] s_Base32Char = { + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', + 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', + 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', + 'y', 'z', '0', '1', '2', '3', '4', '5'}; + + internal static String ToBase32StringSuitableForDirName(byte[] buff) + { + // This routine is optimised to be used with buffs of length 20 + Contract.Assert(((buff.Length % 5) == 0), "Unexpected hash length"); + + StringBuilder sb = StringBuilderCache.Acquire(); + byte b0, b1, b2, b3, b4; + int l, i; + + l = buff.Length; + i = 0; + + // Create l chars using the last 5 bits of each byte. + // Consume 3 MSB bits 5 bytes at a time. + + do + { + b0 = (i < l) ? buff[i++] : (byte)0; + b1 = (i < l) ? buff[i++] : (byte)0; + b2 = (i < l) ? buff[i++] : (byte)0; + b3 = (i < l) ? buff[i++] : (byte)0; + b4 = (i < l) ? buff[i++] : (byte)0; + + // Consume the 5 Least significant bits of each byte + sb.Append(s_Base32Char[b0 & 0x1F]); + sb.Append(s_Base32Char[b1 & 0x1F]); + sb.Append(s_Base32Char[b2 & 0x1F]); + sb.Append(s_Base32Char[b3 & 0x1F]); + sb.Append(s_Base32Char[b4 & 0x1F]); + + // Consume 3 MSB of b0, b1, MSB bits 6, 7 of b3, b4 + sb.Append(s_Base32Char[( + ((b0 & 0xE0) >> 5) | + ((b3 & 0x60) >> 2))]); + + sb.Append(s_Base32Char[( + ((b1 & 0xE0) >> 5) | + ((b4 & 0x60) >> 2))]); + + // Consume 3 MSB bits of b2, 1 MSB bit of b3, b4 + + b2 >>= 5; + + Contract.Assert(((b2 & 0xF8) == 0), "Unexpected set bits"); + + if ((b3 & 0x80) != 0) + b2 |= 0x08; + if ((b4 & 0x80) != 0) + b2 |= 0x10; + + sb.Append(s_Base32Char[b2]); + + } while (i < l); + + return StringBuilderCache.GetStringAndRelease(sb); + } + + // ".." can only be used if it is specified as a part of a valid File/Directory name. We disallow + // the user being able to use it to move up directories. Here are some examples eg + // Valid: a..b abc..d + // Invalid: ..ab ab.. .. abc..d\abc.. + // + internal static void CheckSearchPattern(String searchPattern) + { + int index; + while ((index = searchPattern.IndexOf("..", StringComparison.Ordinal)) != -1) { + + if (index + 2 == searchPattern.Length) // Terminal ".." . Files names cannot end in ".." + throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern")); + + if ((searchPattern[index+2] == DirectorySeparatorChar) + || (searchPattern[index+2] == AltDirectorySeparatorChar)) + throw new ArgumentException(Environment.GetResourceString("Arg_InvalidSearchPattern")); + + searchPattern = searchPattern.Substring(index + 2); + } + + } + + internal static void CheckInvalidPathChars(String path, bool checkAdditional = false) + { + if (path == null) + throw new ArgumentNullException("path"); + + if (PathInternal.HasIllegalCharacters(path, checkAdditional)) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidPathChars")); + } + + internal static String InternalCombine(String path1, String path2) { + if (path1==null || path2==null) + throw new ArgumentNullException((path1==null) ? "path1" : "path2"); + Contract.EndContractBlock(); + CheckInvalidPathChars(path1); + CheckInvalidPathChars(path2); + + if (path2.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_PathEmpty"), "path2"); + if (IsPathRooted(path2)) + throw new ArgumentException(Environment.GetResourceString("Arg_Path2IsRooted"), "path2"); + int i = path1.Length; + if (i == 0) return path2; + char ch = path1[i - 1]; + if (ch != DirectorySeparatorChar && ch != AltDirectorySeparatorChar && ch != VolumeSeparatorChar) + return path1 + DirectorySeparatorCharAsString + path2; + return path1 + path2; + } + + } +} diff --git a/src/mscorlib/src/System/IO/PathHelper.cs b/src/mscorlib/src/System/IO/PathHelper.cs new file mode 100644 index 0000000000..8e39b3c537 --- /dev/null +++ b/src/mscorlib/src/System/IO/PathHelper.cs @@ -0,0 +1,448 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +#if FEATURE_PATHCOMPAT +using System; +using System.Collections; +using System.Text; +using Microsoft.Win32; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Runtime.Versioning; +using System.Security; +using System.Security.Permissions; +using System.Diagnostics.Contracts; + +namespace System.IO { + + // ABOUT: + // Helps with path normalization; support allocating on the stack or heap + // + // PathHelper can't stackalloc the array for obvious reasons; you must pass + // in an array of chars allocated on the stack. + // + // USAGE: + // Suppose you need to represent a char array of length len. Then this is the + // suggested way to instantiate PathHelper: + // *************************************************************************** + // PathHelper pathHelper; + // if (charArrayLength less than stack alloc threshold == Path.MaxPath) + // char* arrayPtr = stackalloc char[Path.MaxPath]; + // pathHelper = new PathHelper(arrayPtr); + // else + // pathHelper = new PathHelper(capacity, maxPath); + // *************************************************************************** + // + // note in the StringBuilder ctor: + // - maxPath may be greater than Path.MaxPath (for isolated storage) + // - capacity may be greater than maxPath. This is even used for non-isolated + // storage scenarios where we want to temporarily allow strings greater + // than Path.MaxPath if they can be normalized down to Path.MaxPath. This + // can happen if the path contains escape characters "..". + // + unsafe internal struct PathHelper { // should not be serialized + + // maximum size, max be greater than max path if contains escape sequence + private int m_capacity; + // current length (next character position) + private int m_length; + // max path, may be less than capacity + private int m_maxPath; + + // ptr to stack alloc'd array of chars + [SecurityCritical] + private char* m_arrayPtr; + + // StringBuilder + private StringBuilder m_sb; + + // whether to operate on stack alloc'd or heap alloc'd array + private bool useStackAlloc; + + // Whether to skip calls to Win32Native.GetLongPathName becasue we tried before and failed: + private bool doNotTryExpandShortFileName; + + // Instantiates a PathHelper with a stack alloc'd array of chars + [System.Security.SecurityCritical] + internal PathHelper(char* charArrayPtr, int length) { + Contract.Requires(charArrayPtr != null); + // force callers to be aware of this + Contract.Requires(length == Path.MaxPath); + this.m_length = 0; + this.m_sb = null; + + this.m_arrayPtr = charArrayPtr; + this.m_capacity = length; + this.m_maxPath = Path.MaxPath; + useStackAlloc = true; + doNotTryExpandShortFileName = false; + } + + // Instantiates a PathHelper with a heap alloc'd array of ints. Will create a StringBuilder + [System.Security.SecurityCritical] + internal PathHelper(int capacity, int maxPath) + { + this.m_length = 0; + this.m_arrayPtr = null; + this.useStackAlloc = false; + + this.m_sb = new StringBuilder(capacity); + this.m_capacity = capacity; + this.m_maxPath = maxPath; + doNotTryExpandShortFileName = false; + } + + internal int Length { + get { + if (useStackAlloc) { + return m_length; + } + else { + return m_sb.Length; + } + } + set { + if (useStackAlloc) { + m_length = value; + } + else { + m_sb.Length = value; + } + } + } + + internal int Capacity { + get { + return m_capacity; + } + } + + internal char this[int index] { + [System.Security.SecurityCritical] + get { + Contract.Requires(index >= 0 && index < Length); + if (useStackAlloc) { + return m_arrayPtr[index]; + } + else { + return m_sb[index]; + } + } + [System.Security.SecurityCritical] + set { + Contract.Requires(index >= 0 && index < Length); + if (useStackAlloc) { + m_arrayPtr[index] = value; + } + else { + m_sb[index] = value; + } + } + } + + [System.Security.SecurityCritical] + internal unsafe void Append(char value) { + if (Length + 1 >= m_capacity) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + if (useStackAlloc) { + m_arrayPtr[Length] = value; + m_length++; + } + else { + m_sb.Append(value); + } + } + + [System.Security.SecurityCritical] + internal unsafe int GetFullPathName() { + if (useStackAlloc) { + char* finalBuffer = stackalloc char[Path.MaxPath + 1]; + int result = Win32Native.GetFullPathName(m_arrayPtr, Path.MaxPath + 1, finalBuffer, IntPtr.Zero); + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (result > Path.MaxPath) { + char* tempBuffer = stackalloc char[result]; + finalBuffer = tempBuffer; + result = Win32Native.GetFullPathName(m_arrayPtr, result, finalBuffer, IntPtr.Zero); + } + + // Full path is genuinely long + if (result >= Path.MaxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + Contract.Assert(result < Path.MaxPath, "did we accidently remove a PathTooLongException check?"); + if (result == 0 && m_arrayPtr[0] != '\0') { + __Error.WinIOError(); + } + + else if (result < Path.MaxPath) { + // Null terminate explicitly (may be only needed for some cases such as empty strings) + // GetFullPathName return length doesn't account for null terminating char... + finalBuffer[result] = '\0'; // Safe to write directly as result is < Path.MaxPath + } + + // We have expanded the paths and GetLongPathName may or may not behave differently from before. + // We need to call it again to see: + doNotTryExpandShortFileName = false; + + String.wstrcpy(m_arrayPtr, finalBuffer, result); + // Doesn't account for null terminating char. Think of this as the last + // valid index into the buffer but not the length of the buffer + Length = result; + return result; + } + else { + StringBuilder finalBuffer = new StringBuilder(m_capacity + 1); + int result = Win32Native.GetFullPathName(m_sb.ToString(), m_capacity + 1, finalBuffer, IntPtr.Zero); + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (result > m_maxPath) { + finalBuffer.Length = result; + result = Win32Native.GetFullPathName(m_sb.ToString(), result, finalBuffer, IntPtr.Zero); + } + + // Fullpath is genuinely long + if (result >= m_maxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + Contract.Assert(result < m_maxPath, "did we accidentally remove a PathTooLongException check?"); + if (result == 0 && m_sb[0] != '\0') { + if (Length >= m_maxPath) { + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + } + __Error.WinIOError(); + } + + // We have expanded the paths and GetLongPathName may or may not behave differently from before. + // We need to call it again to see: + doNotTryExpandShortFileName = false; + + m_sb = finalBuffer; + return result; + } + } + + [System.Security.SecurityCritical] + internal unsafe bool TryExpandShortFileName() { + + if (doNotTryExpandShortFileName) + return false; + + if (useStackAlloc) { + NullTerminate(); + char* buffer = UnsafeGetArrayPtr(); + char* shortFileNameBuffer = stackalloc char[Path.MaxPath + 1]; + + int r = Win32Native.GetLongPathName(buffer, shortFileNameBuffer, Path.MaxPath); + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (r >= Path.MaxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + if (r == 0) { + // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a + // path like \\.\PHYSICALDEVICE0 - some device driver doesn't + // support GetLongPathName on that string. This behavior is + // by design, according to the Core File Services team. + // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs + // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ + + // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: + int lastErr = Marshal.GetLastWin32Error(); + if (lastErr == Win32Native.ERROR_FILE_NOT_FOUND || lastErr == Win32Native.ERROR_PATH_NOT_FOUND) + doNotTryExpandShortFileName = true; + + return false; + } + + // Safe to copy as we have already done Path.MaxPath bound checking + String.wstrcpy(buffer, shortFileNameBuffer, r); + Length = r; + // We should explicitly null terminate as in some cases the long version of the path + // might actually be shorter than what we started with because of Win32's normalization + // Safe to write directly as bufferLength is guaranteed to be < Path.MaxPath + NullTerminate(); + return true; + } + else { + StringBuilder sb = GetStringBuilder(); + + String origName = sb.ToString(); + String tempName = origName; + bool addedPrefix = false; + if (tempName.Length > Path.MaxPath) { + tempName = Path.AddLongPathPrefix(tempName); + addedPrefix = true; + } + sb.Capacity = m_capacity; + sb.Length = 0; + int r = Win32Native.GetLongPathName(tempName, sb, m_capacity); + + if (r == 0) { + // Note: GetLongPathName will return ERROR_INVALID_FUNCTION on a + // path like \\.\PHYSICALDEVICE0 - some device driver doesn't + // support GetLongPathName on that string. This behavior is + // by design, according to the Core File Services team. + // We also get ERROR_NOT_ENOUGH_QUOTA in SQL_CLR_STRESS runs + // intermittently on paths like D:\DOCUME~1\user\LOCALS~1\Temp\ + + // We do not need to call GetLongPathName if we know it will fail becasue the path does not exist: + int lastErr = Marshal.GetLastWin32Error(); + if (Win32Native.ERROR_FILE_NOT_FOUND == lastErr || Win32Native.ERROR_PATH_NOT_FOUND == lastErr) + doNotTryExpandShortFileName = true; + + sb.Length = 0; + sb.Append(origName); + return false; + } + + if (addedPrefix) + r -= 4; + + // If success, the return buffer length does not account for the terminating null character. + // If in-sufficient buffer, the return buffer length does account for the path + the terminating null character. + // If failure, the return buffer length is zero + if (r >= m_maxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + + sb = Path.RemoveLongPathPrefix(sb); + Length = sb.Length; + return true; + + } + } + + [System.Security.SecurityCritical] + internal unsafe void Fixup(int lenSavedName, int lastSlash) { + if (useStackAlloc) { + char* savedName = stackalloc char[lenSavedName]; + String.wstrcpy(savedName, m_arrayPtr + lastSlash + 1, lenSavedName); + Length = lastSlash; + NullTerminate(); + doNotTryExpandShortFileName = false; + bool r = TryExpandShortFileName(); + // Clean up changes made to the newBuffer. + Append(Path.DirectorySeparatorChar); + if (Length + lenSavedName >= Path.MaxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + String.wstrcpy(m_arrayPtr + Length, savedName, lenSavedName); + Length = Length + lenSavedName; + + } + else { + String savedName = m_sb.ToString(lastSlash + 1, lenSavedName); + Length = lastSlash; + doNotTryExpandShortFileName = false; + bool r = TryExpandShortFileName(); + // Clean up changes made to the newBuffer. + Append(Path.DirectorySeparatorChar); + if (Length + lenSavedName >= m_maxPath) + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + m_sb.Append(savedName); + } + } + + [System.Security.SecurityCritical] + internal unsafe bool OrdinalStartsWith(String compareTo, bool ignoreCase) { + if (Length < compareTo.Length) + return false; + + if (useStackAlloc) { + NullTerminate(); + if (ignoreCase) { + String s = new String(m_arrayPtr, 0, compareTo.Length); + return compareTo.Equals(s, StringComparison.OrdinalIgnoreCase); + } + else { + for (int i = 0; i < compareTo.Length; i++) { + if (m_arrayPtr[i] != compareTo[i]) { + return false; + } + } + return true; + } + } + else { + if (ignoreCase) { + return m_sb.ToString().StartsWith(compareTo, StringComparison.OrdinalIgnoreCase); + } + else { + return m_sb.ToString().StartsWith(compareTo, StringComparison.Ordinal); + } + } + } + + [System.Security.SecurityCritical] + private unsafe bool OrdinalEqualsStackAlloc(String compareTo) + { + Contract.Requires(useStackAlloc, "Currently no efficient implementation for StringBuilder.OrdinalEquals(String)"); + + if (Length != compareTo.Length) { + return false; + } + + for (int i = 0; i < compareTo.Length; i++) { + if (m_arrayPtr[i] != compareTo[i]) { + return false; + } + } + + return true; + } + + [System.Security.SecuritySafeCritical] + public override String ToString() { + if (useStackAlloc) { + return new String(m_arrayPtr, 0, Length); + } + else { + return m_sb.ToString(); + } + } + + [System.Security.SecuritySafeCritical] + internal String ToStringOrExisting(String existingString) + { + if (useStackAlloc) { + return OrdinalEqualsStackAlloc(existingString) ? + existingString : + new String(m_arrayPtr, 0, Length); + } + else { + string newString = m_sb.ToString(); // currently no good StringBuilder.OrdinalEquals(string) + return String.Equals(newString, existingString, StringComparison.Ordinal) ? + existingString : + newString; + } + } + + [System.Security.SecurityCritical] + private unsafe char* UnsafeGetArrayPtr() { + Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder"); + return m_arrayPtr; + } + + private StringBuilder GetStringBuilder() { + Contract.Requires(!useStackAlloc, "This should never be called for PathHelpers that wrap a stackalloc'd buffer"); + return m_sb; + } + + [System.Security.SecurityCritical] + private unsafe void NullTerminate() { + Contract.Requires(useStackAlloc, "This should never be called for PathHelpers wrapping a StringBuilder"); + m_arrayPtr[m_length] = '\0'; + } + + } +} +#endif // FEATURE_PATHCOMPAT
\ No newline at end of file diff --git a/src/mscorlib/src/System/IO/PathInternal.cs b/src/mscorlib/src/System/IO/PathInternal.cs new file mode 100644 index 0000000000..3970e2264a --- /dev/null +++ b/src/mscorlib/src/System/IO/PathInternal.cs @@ -0,0 +1,806 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Microsoft.Win32; +using System; +using System.Diagnostics.Contracts; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Text; + +namespace System.IO +{ + /// <summary>Contains internal path helpers that are shared between many projects.</summary> + internal static class PathInternal + { + internal const string ExtendedPathPrefix = @"\\?\"; + internal const string UncPathPrefix = @"\\"; + internal const string UncExtendedPrefixToInsert = @"?\UNC\"; + internal const string UncExtendedPathPrefix = @"\\?\UNC\"; + internal const string DevicePathPrefix = @"\\.\"; + // \\?\, \\.\, \??\ + internal const int DevicePrefixLength = 4; + // \\ + internal const int UncPrefixLength = 2; + // \\?\UNC\, \\.\UNC\ + internal const int UncExtendedPrefixLength = 8; +#if !PLATFORM_UNIX + internal const int MaxShortPath = 260; + internal const int MaxShortDirectoryPath = 248; +#else + internal const int MaxShortPath = 1024; + internal const int MaxShortDirectoryPath = MaxShortPath; +#endif + + // Windows is limited in long paths by the max size of its internal representation of a unicode string. + // UNICODE_STRING has a max length of USHORT in _bytes_ without a trailing null. + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx + internal const int MaxLongPath = short.MaxValue; + internal static readonly int MaxComponentLength = 255; + +#if !PLATFORM_UNIX + internal static readonly char[] InvalidPathChars = + { + '\"', '<', '>', '|', '\0', + (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, + (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, + (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, + (char)31 + }; +#else + internal static readonly char[] InvalidPathChars = { '\0' }; +#endif + + + /// <summary> + /// Validates volume separator only occurs as C: or \\?\C:. This logic is meant to filter out Alternate Data Streams. + /// </summary> + /// <returns>True if the path has an invalid volume separator.</returns> + internal static bool HasInvalidVolumeSeparator(string path) + { + // Toss out paths with colons that aren't a valid drive specifier. + // Cannot start with a colon and can only be of the form "C:" or "\\?\C:". + // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.) + + // We don't care about skipping starting space for extended paths. Assume no knowledge of extended paths if we're forcing old path behavior. + bool isExtended = +#if FEATURE_PATHCOMPAT + !AppContextSwitches.UseLegacyPathHandling && +#endif + IsExtended(path); + int startIndex = isExtended ? ExtendedPathPrefix.Length : PathStartSkip(path); + + // If we start with a colon + if ((path.Length > startIndex && path[startIndex] == Path.VolumeSeparatorChar) + // Or have an invalid drive letter and colon + || (path.Length >= startIndex + 2 && path[startIndex + 1] == Path.VolumeSeparatorChar && !IsValidDriveChar(path[startIndex])) + // Or have any colons beyond the drive colon + || (path.Length > startIndex + 2 && path.IndexOf(Path.VolumeSeparatorChar, startIndex + 2) != -1)) + { + return true; + } + + return false; + } + + /// <summary> + /// Returns true if the given StringBuilder starts with the given value. + /// </summary> + /// <param name="value">The string to compare against the start of the StringBuilder.</param> + internal static bool StartsWithOrdinal(StringBuilder builder, string value, bool ignoreCase = false) + { + if (value == null || builder.Length < value.Length) + return false; + + if (ignoreCase) + { + for (int i = 0; i < value.Length; i++) + if (char.ToUpperInvariant(builder[i]) != char.ToUpperInvariant(value[i])) return false; + } + else + { + for (int i = 0; i < value.Length; i++) + if (builder[i] != value[i]) return false; + } + + return true; + } + + /// <summary> + /// Returns true if the given character is a valid drive letter + /// </summary> + internal static bool IsValidDriveChar(char value) + { + return ((value >= 'A' && value <= 'Z') || (value >= 'a' && value <= 'z')); + } + + /// <summary> + /// Returns true if the path is too long + /// </summary> + internal static bool IsPathTooLong(string fullPath) + { + // We'll never know precisely what will fail as paths get changed internally in Windows and + // may grow beyond / shrink below exceed MaxLongPath. +#if FEATURE_PATHCOMPAT + if (AppContextSwitches.BlockLongPaths) + { + // We allow paths of any length if extended (and not in compat mode) + if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath)) + return fullPath.Length >= MaxShortPath; + } +#endif + + return fullPath.Length >= MaxLongPath; + } + + /// <summary> + /// Return true if any path segments are too long + /// </summary> + internal static bool AreSegmentsTooLong(string fullPath) + { + int length = fullPath.Length; + int lastSeparator = 0; + + for (int i = 0; i < length; i++) + { + if (IsDirectorySeparator(fullPath[i])) + { + if (i - lastSeparator > MaxComponentLength) + return true; + lastSeparator = i; + } + } + + if (length - 1 - lastSeparator > MaxComponentLength) + return true; + + return false; + } + + /// <summary> + /// Returns true if the directory is too long + /// </summary> + internal static bool IsDirectoryTooLong(string fullPath) + { +#if FEATURE_PATHCOMPAT + if (AppContextSwitches.BlockLongPaths) + { + // We allow paths of any length if extended (and not in compat mode) + if (AppContextSwitches.UseLegacyPathHandling || !IsExtended(fullPath)) + return (fullPath.Length >= MaxShortDirectoryPath); + } +#endif + + return IsPathTooLong(fullPath); + } + + /// <summary> + /// Adds the extended path prefix (\\?\) if not relative or already a device path. + /// </summary> + internal static string EnsureExtendedPrefix(string path) + { + // Putting the extended prefix on the path changes the processing of the path. It won't get normalized, which + // means adding to relative paths will prevent them from getting the appropriate current directory inserted. + + // If it already has some variant of a device path (\??\, \\?\, \\.\, //./, etc.) we don't need to change it + // as it is either correct or we will be changing the behavior. When/if Windows supports long paths implicitly + // in the future we wouldn't want normalization to come back and break existing code. + + // In any case, all internal usages should be hitting normalize path (Path.GetFullPath) before they hit this + // shimming method. (Or making a change that doesn't impact normalization, such as adding a filename to a + // normalized base path.) + if (IsPartiallyQualified(path) || IsDevice(path)) + return path; + + // Given \\server\share in longpath becomes \\?\UNC\server\share + if (path.StartsWith(UncPathPrefix, StringComparison.OrdinalIgnoreCase)) + return path.Insert(2, UncExtendedPrefixToInsert); + + return ExtendedPathPrefix + path; + } + + /// <summary> + /// Removes the extended path prefix (\\?\) if present. + /// </summary> + internal static string RemoveExtendedPrefix(string path) + { + if (!IsExtended(path)) + return path; + + // Given \\?\UNC\server\share we return \\server\share + if (IsExtendedUnc(path)) + return path.Remove(2, 6); + + return path.Substring(DevicePrefixLength); + } + + /// <summary> + /// Removes the extended path prefix (\\?\) if present. + /// </summary> + internal static StringBuilder RemoveExtendedPrefix(StringBuilder path) + { + if (!IsExtended(path)) + return path; + + // Given \\?\UNC\server\share we return \\server\share + if (IsExtendedUnc(path)) + return path.Remove(2, 6); + + return path.Remove(0, DevicePrefixLength); + } + + /// <summary> + /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") + /// </summary> + internal static bool IsDevice(string path) + { + // If the path begins with any two separators it will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + /// <summary> + /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\") + /// </summary> + internal static bool IsDevice(StringBuffer path) + { + // If the path begins with any two separators it will be recognized and normalized and prepped with + // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not. + return IsExtended(path) + || + ( + path.Length >= DevicePrefixLength + && IsDirectorySeparator(path[0]) + && IsDirectorySeparator(path[1]) + && (path[2] == '.' || path[2] == '?') + && IsDirectorySeparator(path[3]) + ); + } + + /// <summary> + /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the + /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization + /// and path length checks. + /// </summary> + internal static bool IsExtended(string path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// <summary> + /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the + /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization + /// and path length checks. + /// </summary> + internal static bool IsExtended(StringBuilder path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// <summary> + /// Returns true if the path uses the canonical form of extended syntax ("\\?\" or "\??\"). If the + /// path matches exactly (cannot use alternate directory separators) Windows will skip normalization + /// and path length checks. + /// </summary> + internal static bool IsExtended(StringBuffer path) + { + // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths. + // Skipping of normalization will *only* occur if back slashes ('\') are used. + return path.Length >= DevicePrefixLength + && path[0] == '\\' + && (path[1] == '\\' || path[1] == '?') + && path[2] == '?' + && path[3] == '\\'; + } + + /// <summary> + /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\) + /// </summary> + internal static bool IsExtendedUnc(string path) + { + return path.Length >= UncExtendedPathPrefix.Length + && IsExtended(path) + && char.ToUpper(path[4]) == 'U' + && char.ToUpper(path[5]) == 'N' + && char.ToUpper(path[6]) == 'C' + && path[7] == '\\'; + } + + /// <summary> + /// Returns true if the path uses the extended UNC syntax (\\?\UNC\ or \??\UNC\) + /// </summary> + internal static bool IsExtendedUnc(StringBuilder path) + { + return path.Length >= UncExtendedPathPrefix.Length + && IsExtended(path) + && char.ToUpper(path[4]) == 'U' + && char.ToUpper(path[5]) == 'N' + && char.ToUpper(path[6]) == 'C' + && path[7] == '\\'; + } + + /// <summary> + /// Returns a value indicating if the given path contains invalid characters (", <, >, | + /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31). + /// Does not check for wild card characters ? and *. + /// + /// Will not check if the path is a device path and not in Legacy mode as many of these + /// characters are valid for devices (pipes for example). + /// </summary> + internal static bool HasIllegalCharacters(string path, bool checkAdditional = false) + { + if ( +#if FEATURE_PATHCOMPAT + !AppContextSwitches.UseLegacyPathHandling && +#endif + IsDevice(path)) + { + return false; + } + + return AnyPathHasIllegalCharacters(path, checkAdditional: checkAdditional); + } + + /// <summary> + /// Version of HasIllegalCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care + /// about proper device path handling. + /// </summary> + internal static bool AnyPathHasIllegalCharacters(string path, bool checkAdditional = false) + { + return path.IndexOfAny(InvalidPathChars) >= 0 +#if !PLATFORM_UNIX + || (checkAdditional && AnyPathHasWildCardCharacters(path)) +#endif + ; + } + + /// <summary> + /// Check for ? and *. + /// </summary> + internal static bool HasWildCardCharacters(string path) + { + // Question mark is part of some device paths + int startIndex = +#if FEATURE_PATHCOMPAT + AppContextSwitches.UseLegacyPathHandling ? 0 : +#endif + IsDevice(path) ? ExtendedPathPrefix.Length : 0; + return AnyPathHasWildCardCharacters(path, startIndex: startIndex); + } + + /// <summary> + /// Version of HasWildCardCharacters that checks no AppContextSwitches. Only use if you know you need to skip switches and don't care + /// about proper device path handling. + /// </summary> + internal static bool AnyPathHasWildCardCharacters(string path, int startIndex = 0) + { + char currentChar; + for (int i = startIndex; i < path.Length; i++) + { + currentChar = path[i]; + if (currentChar == '*' || currentChar == '?') return true; + } + return false; + } + + /// <summary> + /// Gets the length of the root of the path (drive, share, etc.). + /// </summary> + [System.Security.SecuritySafeCritical] + internal unsafe static int GetRootLength(string path) + { + fixed (char* value = path) + { + return (int)GetRootLength(value, (ulong)path.Length); + } + } + + /// <summary> + /// Gets the length of the root of the path (drive, share, etc.). + /// </summary> + [System.Security.SecuritySafeCritical] + internal unsafe static uint GetRootLength(StringBuffer path) + { + if (path.Length == 0) return 0; + return GetRootLength(path.CharPointer, path.Length); + } + + [System.Security.SecurityCritical] + private unsafe static uint GetRootLength(char* path, ulong pathLength) + { + uint i = 0; + +#if PLATFORM_UNIX + if (pathLength >= 1 && (IsDirectorySeparator(path[0]))) + i = 1; +#else + uint volumeSeparatorLength = 2; // Length to the colon "C:" + uint uncRootLength = 2; // Length to the start of the server name "\\" + + bool extendedSyntax = StartsWithOrdinal(path, pathLength, ExtendedPathPrefix); + bool extendedUncSyntax = StartsWithOrdinal(path, pathLength, UncExtendedPathPrefix); + if (extendedSyntax) + { + // Shift the position we look for the root from to account for the extended prefix + if (extendedUncSyntax) + { + // "\\" -> "\\?\UNC\" + uncRootLength = (uint)UncExtendedPathPrefix.Length; + } + else + { + // "C:" -> "\\?\C:" + volumeSeparatorLength += (uint)ExtendedPathPrefix.Length; + } + } + + if ((!extendedSyntax || extendedUncSyntax) && pathLength > 0 && IsDirectorySeparator(path[0])) + { + // UNC or simple rooted path (e.g. "\foo", NOT "\\?\C:\foo") + + i = 1; // Drive rooted (\foo) is one character + if (extendedUncSyntax || (pathLength > 1 && IsDirectorySeparator(path[1]))) + { + // UNC (\\?\UNC\ or \\), scan past the next two directory separators at most + // (e.g. to \\?\UNC\Server\Share or \\Server\Share\) + i = uncRootLength; + int n = 2; // Maximum separators to skip + while (i < pathLength && (!IsDirectorySeparator(path[i]) || --n > 0)) i++; + } + } + else if (pathLength >= volumeSeparatorLength && path[volumeSeparatorLength - 1] == Path.VolumeSeparatorChar) + { + // Path is at least longer than where we expect a colon, and has a colon (\\?\A:, A:) + // If the colon is followed by a directory separator, move past it + i = volumeSeparatorLength; + if (pathLength >= volumeSeparatorLength + 1 && IsDirectorySeparator(path[volumeSeparatorLength])) i++; + } +#endif // !PLATFORM_UNIX + return i; + } + + [System.Security.SecurityCritical] + private unsafe static bool StartsWithOrdinal(char* source, ulong sourceLength, string value) + { + if (sourceLength < (ulong)value.Length) return false; + for (int i = 0; i < value.Length; i++) + { + if (value[i] != source[i]) return false; + } + return true; + } + + /// <summary> + /// Returns true if the path specified is relative to the current drive or working directory. + /// Returns false if the path is fixed to a specific drive or UNC path. This method does no + /// validation of the path (URIs will be returned as relative as a result). + /// </summary> + /// <remarks> + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + /// "C:a" is drive relative- meaning that it will be resolved against the current directory + /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory + /// will not be used to modify the path). + /// </remarks> + internal static bool IsPartiallyQualified(string path) + { +#if PLATFORM_UNIX + return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar); +#else + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return true; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return !(path[1] == '?' || IsDirectorySeparator(path[1])); + } + + // The only way to specify a fixed path that doesn't begin with two slashes + // is the drive, colon, slash format- i.e. C:\ + return !((path.Length >= 3) + && (path[1] == Path.VolumeSeparatorChar) + && IsDirectorySeparator(path[2]) + // To match old behavior we'll check the drive character for validity as the path is technically + // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. + && IsValidDriveChar(path[0])); +#endif // !PLATFORM_UNIX + } + + /// <summary> + /// Returns true if the path specified is relative to the current drive or working directory. + /// Returns false if the path is fixed to a specific drive or UNC path. This method does no + /// validation of the path (URIs will be returned as relative as a result). + /// </summary> + /// <remarks> + /// Handles paths that use the alternate directory separator. It is a frequent mistake to + /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case. + /// "C:a" is drive relative- meaning that it will be resolved against the current directory + /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory + /// will not be used to modify the path). + /// </remarks> + internal static bool IsPartiallyQualified(StringBuffer path) + { +#if PLATFORM_UNIX + return !(path.Length >= 1 && path[0] == Path.DirectorySeparatorChar); +#else + if (path.Length < 2) + { + // It isn't fixed, it must be relative. There is no way to specify a fixed + // path with one character (or less). + return true; + } + + if (IsDirectorySeparator(path[0])) + { + // There is no valid way to specify a relative path with two initial slashes or + // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\ + return !(path[1] == '?' || IsDirectorySeparator(path[1])); + } + + // The only way to specify a fixed path that doesn't begin with two slashes + // is the drive, colon, slash format- i.e. C:\ + return !((path.Length >= 3) + && (path[1] == Path.VolumeSeparatorChar) + && IsDirectorySeparator(path[2]) + // To match old behavior we'll check the drive character for validity as the path is technically + // not qualified if you don't have a valid drive. "=:\" is the "=" file's default data stream. + && IsValidDriveChar(path[0])); +#endif // !PLATFORM_UNIX + } + + /// <summary> + /// On Windows, returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator. + /// (examples are " C:", " \") + /// This is a legacy behavior of Path.GetFullPath(). + /// </summary> + /// <remarks> + /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip. + /// </remarks> + internal static int PathStartSkip(string path) + { +#if !PLATFORM_UNIX + int startIndex = 0; + while (startIndex < path.Length && path[startIndex] == ' ') startIndex++; + + if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex])) + || (startIndex + 1 < path.Length && path[startIndex + 1] == Path.VolumeSeparatorChar && IsValidDriveChar(path[startIndex]))) + { + // Go ahead and skip spaces as we're either " C:" or " \" + return startIndex; + } +#endif + + return 0; + } + + /// <summary> + /// True if the given character is a directory separator. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool IsDirectorySeparator(char c) + { + return c == Path.DirectorySeparatorChar +#if !PLATFORM_UNIX + || c == Path.AltDirectorySeparatorChar +#endif + ; + } + + /// <summary> + /// Normalize separators in the given path. Converts forward slashes into back slashes and compresses slash runs, keeping initial 2 if present. + /// Also trims initial whitespace in front of "rooted" paths (see PathStartSkip). + /// + /// This effectively replicates the behavior of the legacy NormalizePath when it was called with fullCheck=false and expandShortpaths=false. + /// The current NormalizePath gets directory separator normalization from Win32's GetFullPathName(), which will resolve relative paths and as + /// such can't be used here (and is overkill for our uses). + /// + /// Like the current NormalizePath this will not try and analyze periods/spaces within directory segments. + /// </summary> + /// <remarks> + /// The only callers that used to use Path.Normalize(fullCheck=false) were Path.GetDirectoryName() and Path.GetPathRoot(). Both usages do + /// not need trimming of trailing whitespace here. + /// + /// GetPathRoot() could technically skip normalizing separators after the second segment- consider as a future optimization. + /// + /// For legacy desktop behavior with ExpandShortPaths: + /// - It has no impact on GetPathRoot() so doesn't need consideration. + /// - It could impact GetDirectoryName(), but only if the path isn't relative (C:\ or \\Server\Share). + /// + /// In the case of GetDirectoryName() the ExpandShortPaths behavior was undocumented and provided inconsistent results if the path was + /// fixed/relative. For example: "C:\PROGRA~1\A.TXT" would return "C:\Program Files" while ".\PROGRA~1\A.TXT" would return ".\PROGRA~1". If you + /// ultimately call GetFullPath() this doesn't matter, but if you don't or have any intermediate string handling could easily be tripped up by + /// this undocumented behavior. + /// </remarks> + internal static string NormalizeDirectorySeparators(string path) + { + if (string.IsNullOrEmpty(path)) return path; + + char current; + int start = PathStartSkip(path); + + if (start == 0) + { + // Make a pass to see if we need to normalize so we can potentially skip allocating + bool normalized = true; + + for (int i = 0; i < path.Length; i++) + { + current = path[i]; + if (IsDirectorySeparator(current) + && (current != Path.DirectorySeparatorChar +#if !PLATFORM_UNIX + // Check for sequential separators past the first position (we need to keep initial two for UNC/extended) + || (i > 0 && i + 1 < path.Length && IsDirectorySeparator(path[i + 1])) +#endif + )) + { + normalized = false; + break; + } + } + + if (normalized) return path; + } + + StringBuilder builder = StringBuilderCache.Acquire(path.Length); + +#if !PLATFORM_UNIX + // On Windows we always keep the first separator, even if the next is a separator (we need to keep initial two for UNC/extended) + if (IsDirectorySeparator(path[start])) + { + start++; + builder.Append(Path.DirectorySeparatorChar); + } +#endif + + for (int i = start; i < path.Length; i++) + { + current = path[i]; + + // If we have a separator + if (IsDirectorySeparator(current)) + { + // If the next is a separator, skip adding this + if (i + 1 < path.Length && IsDirectorySeparator(path[i + 1])) + { + continue; + } + + // Ensure it is the primary separator + current = Path.DirectorySeparatorChar; + } + + builder.Append(current); + } + + return StringBuilderCache.GetStringAndRelease(builder); + } + +#if PLATFORM_UNIX + // We rely on Windows to remove relative segments on Windows. This would need to be updated to + // handle the proper rooting on Windows if we for some reason need it. + + /// <summary> + /// Try to remove relative segments from the given path (without combining with a root). + /// </summary> + /// <param name="skip">Skip the specified number of characters before evaluating.</param> + internal static string RemoveRelativeSegments(string path, int skip = 0) + { + bool flippedSeparator = false; + + // Remove "//", "/./", and "/../" from the path by copying each character to the output, + // except the ones we're removing, such that the builder contains the normalized path + // at the end. + var sb = StringBuilderCache.Acquire(path.Length); + if (skip > 0) + { + sb.Append(path, 0, skip); + } + + int componentCharCount = 0; + for (int i = skip; i < path.Length; i++) + { + char c = path[i]; + + if (PathInternal.IsDirectorySeparator(c) && i + 1 < path.Length) + { + componentCharCount = 0; + + // Skip this character if it's a directory separator and if the next character is, too, + // e.g. "parent//child" => "parent/child" + if (PathInternal.IsDirectorySeparator(path[i + 1])) + { + continue; + } + + // Skip this character and the next if it's referring to the current directory, + // e.g. "parent/./child" =? "parent/child" + if ((i + 2 == path.Length || PathInternal.IsDirectorySeparator(path[i + 2])) && + path[i + 1] == '.') + { + i++; + continue; + } + + // Skip this character and the next two if it's referring to the parent directory, + // e.g. "parent/child/../grandchild" => "parent/grandchild" + if (i + 2 < path.Length && + (i + 3 == path.Length || PathInternal.IsDirectorySeparator(path[i + 3])) && + path[i + 1] == '.' && path[i + 2] == '.') + { + // Unwind back to the last slash (and if there isn't one, clear out everything). + int s; + for (s = sb.Length - 1; s >= 0; s--) + { + if (PathInternal.IsDirectorySeparator(sb[s])) + { + sb.Length = s; + break; + } + } + if (s < 0) + { + sb.Length = 0; + } + + i += 2; + continue; + } + } + + if (++componentCharCount > PathInternal.MaxComponentLength) + { + throw new PathTooLongException(); + } + + // Normalize the directory separator if needed + if (c != Path.DirectorySeparatorChar && c == Path.AltDirectorySeparatorChar) + { + c = Path.DirectorySeparatorChar; + flippedSeparator = true; + } + + sb.Append(c); + } + + if (flippedSeparator || sb.Length != path.Length) + { + return StringBuilderCache.GetStringAndRelease(sb); + } + else + { + // We haven't changed the source path, return the original + StringBuilderCache.Release(sb); + return path; + } + } +#endif // PLATFORM_UNIX + } +}
\ No newline at end of file diff --git a/src/mscorlib/src/System/IO/PathTooLongException.cs b/src/mscorlib/src/System/IO/PathTooLongException.cs new file mode 100644 index 0000000000..b1063232a1 --- /dev/null +++ b/src/mscorlib/src/System/IO/PathTooLongException.cs @@ -0,0 +1,44 @@ +// 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: Exception for paths and/or filenames that are +** too long. +** +** +===========================================================*/ + +using System; +using System.Runtime.Serialization; + +namespace System.IO { + + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class PathTooLongException : IOException + { + public PathTooLongException() + : base(Environment.GetResourceString("IO.PathTooLong")) { + SetErrorCode(__HResults.COR_E_PATHTOOLONG); + } + + public PathTooLongException(String message) + : base(message) { + SetErrorCode(__HResults.COR_E_PATHTOOLONG); + } + + public PathTooLongException(String message, Exception innerException) + : base(message, innerException) { + SetErrorCode(__HResults.COR_E_PATHTOOLONG); + } + + protected PathTooLongException(SerializationInfo info, StreamingContext context) : base (info, context) { + } + } +} diff --git a/src/mscorlib/src/System/IO/PinnedBufferMemoryStream.cs b/src/mscorlib/src/System/IO/PinnedBufferMemoryStream.cs new file mode 100644 index 0000000000..4fd54f57f5 --- /dev/null +++ b/src/mscorlib/src/System/IO/PinnedBufferMemoryStream.cs @@ -0,0 +1,73 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Pins a byte[], exposing it as an unmanaged memory +** stream. Used in ResourceReader for corner cases. +** +** +===========================================================*/ +using System; +using System.Runtime.InteropServices; +using System.Diagnostics.Contracts; + +namespace System.IO { + internal sealed unsafe class PinnedBufferMemoryStream : UnmanagedMemoryStream + { + private byte[] _array; + private GCHandle _pinningHandle; + + // The new inheritance model requires a Critical default ctor since base (UnmanagedMemoryStream) has one + [System.Security.SecurityCritical] + private PinnedBufferMemoryStream():base(){} + + [System.Security.SecurityCritical] // auto-generated + internal PinnedBufferMemoryStream(byte[] array) + { + Contract.Assert(array != null, "Array can't be null"); + + int len = array.Length; + // Handle 0 length byte arrays specially. + if (len == 0) { + array = new byte[1]; + len = 0; + } + + _array = array; + _pinningHandle = new GCHandle(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... + fixed(byte* ptr = _array) + Initialize(ptr, len, len, FileAccess.Read, true); + } + + ~PinnedBufferMemoryStream() + { + Dispose(false); + } + + [System.Security.SecuritySafeCritical] // auto-generated + protected override void Dispose(bool disposing) + { + if (_isOpen) { + _pinningHandle.Free(); + _isOpen = false; + } +#if _DEBUG + // To help track down lifetime issues on checked builds, force + //a full GC here. + if (disposing) { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } +#endif + base.Dispose(disposing); + } + } +} diff --git a/src/mscorlib/src/System/IO/ReadLinesIterator.cs b/src/mscorlib/src/System/IO/ReadLinesIterator.cs new file mode 100644 index 0000000000..ce2ad2ad0f --- /dev/null +++ b/src/mscorlib/src/System/IO/ReadLinesIterator.cs @@ -0,0 +1,102 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; +using System.Diagnostics.Contracts; +using System.Runtime.Versioning; +using System.Text; + +namespace System.IO +{ + // An iterator that returns a single line at-a-time from a given file. + // + // Known issues which cannot be changed to remain compatible with 4.0: + // + // - The underlying StreamReader is allocated upfront for the IEnumerable<T> before + // GetEnumerator has even been called. While this is good in that exceptions such as + // DirectoryNotFoundException and FileNotFoundException are thrown directly by + // File.ReadLines (which the user probably expects), it also means that the reader + // will be leaked if the user never actually foreach's over the enumerable (and hence + // calls Dispose on at least one IEnumerator<T> instance). + // + // - Reading to the end of the IEnumerator<T> disposes it. This means that Dispose + // is called twice in a normal foreach construct. + // + // - IEnumerator<T> instances from the same IEnumerable<T> party on the same underlying + // reader (Dev10 Bugs 904764). + // + internal class ReadLinesIterator : Iterator<string> + { + private readonly string _path; + private readonly Encoding _encoding; + private StreamReader _reader; + + private ReadLinesIterator(string path, Encoding encoding, StreamReader reader) + { + Contract.Requires(path != null); + Contract.Requires(path.Length > 0); + Contract.Requires(encoding != null); + Contract.Requires(reader != null); + + _path = path; + _encoding = encoding; + _reader = reader; + } + + public override bool MoveNext() + { + if (this._reader != null) + { + this.current = _reader.ReadLine(); + if (this.current != null) + return true; + + // To maintain 4.0 behavior we Dispose + // after reading to the end of the reader. + Dispose(); + } + + return false; + } + + protected override Iterator<string> Clone() + { + // NOTE: To maintain the same behavior with the previous yield-based + // iterator in 4.0, we have all the IEnumerator<T> instances share the same + // underlying reader. If we have already been disposed, _reader will be null, + // which will cause CreateIterator to simply new up a new instance to start up + // a new iteration. Dev10 Bugs 904764 has been filed to fix this in next side- + // by-side release. + return CreateIterator(_path, _encoding, _reader); + } + + protected override void Dispose(bool disposing) + { + try + { + if (disposing) + { + if (_reader != null) + { + _reader.Dispose(); + } + } + } + finally + { + _reader = null; + base.Dispose(disposing); + } + } + + internal static ReadLinesIterator CreateIterator(string path, Encoding encoding) + { + return CreateIterator(path, encoding, (StreamReader)null); + } + + private static ReadLinesIterator CreateIterator(string path, Encoding encoding, StreamReader reader) + { + return new ReadLinesIterator(path, encoding, reader ?? new StreamReader(path, encoding)); + } + } +} diff --git a/src/mscorlib/src/System/IO/SearchOption.cs b/src/mscorlib/src/System/IO/SearchOption.cs new file mode 100644 index 0000000000..aa9b9e37f8 --- /dev/null +++ b/src/mscorlib/src/System/IO/SearchOption.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** Enum: SearchOption +** +** +** +** +** Purpose: Enum describing whether the search operation should +** retrieve files/directories from the current directory alone +** or should include all the subdirectories also. +** +** +===========================================================*/ + +using System; + +namespace System.IO { + [Serializable] +[System.Runtime.InteropServices.ComVisible(true)] + public enum SearchOption + { + // Include only the current directory in the search operation + TopDirectoryOnly, + + // Include the current directory and all the sub-directories + // underneath it including reparse points in the search operation. + // This will traverse reparse points (i.e, mounted points and symbolic links) + // recursively. If the directory structure searched contains a loop + // because of hard links, the search operation will go on for ever. + AllDirectories, + } +} diff --git a/src/mscorlib/src/System/IO/SeekOrigin.cs b/src/mscorlib/src/System/IO/SeekOrigin.cs new file mode 100644 index 0000000000..a0a013e7aa --- /dev/null +++ b/src/mscorlib/src/System/IO/SeekOrigin.cs @@ -0,0 +1,32 @@ +// 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. + +/*============================================================ +** +** Enum: SeekOrigin +** +** +** +** +** Purpose: Enum describing locations in a stream you could +** seek relative to. +** +** +===========================================================*/ + +using System; + +namespace System.IO { + // Provides seek reference points. To seek to the end of a stream, + // call stream.Seek(0, SeekOrigin.End). + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public enum SeekOrigin + { + // These constants match Win32's FILE_BEGIN, FILE_CURRENT, and FILE_END + Begin = 0, + Current = 1, + End = 2, + } +} diff --git a/src/mscorlib/src/System/IO/Stream.cs b/src/mscorlib/src/System/IO/Stream.cs new file mode 100644 index 0000000000..a1f29364cb --- /dev/null +++ b/src/mscorlib/src/System/IO/Stream.cs @@ -0,0 +1,1304 @@ +// 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; +using System.Threading; +using System.Threading.Tasks; + +using System.Runtime; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.ExceptionServices; +using System.Security; +using System.Security.Permissions; +using System.Diagnostics.Contracts; +using System.Reflection; + +namespace System.IO { + [Serializable] + [ComVisible(true)] +#if FEATURE_REMOTING + public abstract class Stream : MarshalByRefObject, IDisposable { +#else // FEATURE_REMOTING + public abstract class Stream : IDisposable { +#endif // FEATURE_REMOTING + + 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 + + [NonSerialized] + private ReadWriteTask _activeReadWriteTask; + [NonSerialized] + 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 { + [Pure] + get; + } + + // If CanSeek is false, Position, Seek, Length, and SetLength should throw. + public abstract bool CanSeek { + [Pure] + get; + } + + [ComVisible(false)] + public virtual bool CanTimeout { + [Pure] + get { + return false; + } + } + + public abstract bool CanWrite { + [Pure] + get; + } + + public abstract long Length { + get; + } + + public abstract long Position { + get; + set; + } + + [ComVisible(false)] + public virtual int ReadTimeout { + get { + Contract.Ensures(Contract.Result<int>() >= 0); + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported")); + } + set { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported")); + } + } + + [ComVisible(false)] + public virtual int WriteTimeout { + get { + Contract.Ensures(Contract.Result<int>() >= 0); + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported")); + } + set { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_TimeoutsNotSupported")); + } + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public Task CopyToAsync(Stream destination) + { + int bufferSize = _DefaultCopyBufferSize; + +#if FEATURE_CORECLR + if (CanSeek) + { + long length = Length; + long position = Position; + if (length <= position) // Handles negative overflows + { + // If we go down this branch, it means there are + // no bytes left in this stream. + + // Ideally we would just return Task.CompletedTask here, + // but CopyToAsync(Stream, int, CancellationToken) was already + // virtual at the time this optimization was introduced. So + // if it does things like argument validation (checking if destination + // is null and throwing an exception), then await fooStream.CopyToAsync(null) + // would no longer throw if there were no bytes left. On the other hand, + // we also can't roll our own argument validation and return Task.CompletedTask, + // because it would be a breaking change if the stream's override didn't throw before, + // or in a different order. So for simplicity, we just set the bufferSize to 1 + // (not 0 since the default implementation throws for 0) and forward to the virtual method. + 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); + } + } +#endif // FEATURE_CORECLR + + return CopyToAsync(destination, bufferSize); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public Task CopyToAsync(Stream destination, Int32 bufferSize) + { + return CopyToAsync(destination, bufferSize, CancellationToken.None); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public virtual Task CopyToAsync(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) + { + ValidateCopyToArguments(destination, bufferSize); + + return CopyToAsyncInternal(destination, bufferSize, cancellationToken); + } + + private async Task CopyToAsyncInternal(Stream destination, Int32 bufferSize, CancellationToken cancellationToken) + { + Contract.Requires(destination != null); + Contract.Requires(bufferSize > 0); + Contract.Requires(CanRead); + Contract.Requires(destination.CanWrite); + + byte[] buffer = new byte[bufferSize]; + int bytesRead; + while ((bytesRead = await ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0) + { + await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false); + } + } + + // 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 = _DefaultCopyBufferSize; + +#if FEATURE_CORECLR + if (CanSeek) + { + long length = Length; + long position = Position; + if (length <= position) // Handles negative overflows + { + // No bytes left in stream + // Call the other overload with a bufferSize of 1, + // in case it's made virtual in the future + 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); + } + } +#endif // FEATURE_CORECLR + + CopyTo(destination, bufferSize); + } + + public virtual void CopyTo(Stream destination, int bufferSize) + { + ValidateCopyToArguments(destination, bufferSize); + + byte[] buffer = new byte[bufferSize]; + int read; + while ((read = Read(buffer, 0, buffer.Length)) != 0) + destination.Write(buffer, 0, read); + } + + // 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 starting in V2. + public virtual void Close() + { + /* These are correct, but we'd have to fix PipeStream & NetworkStream very carefully. + Contract.Ensures(CanRead == false); + Contract.Ensures(CanWrite == false); + Contract.Ensures(CanSeek == false); + */ + + Dispose(true); + GC.SuppressFinalize(this); + } + + public void Dispose() + { + /* These are correct, but we'd have to fix PipeStream & NetworkStream very carefully. + Contract.Ensures(CanRead == false); + Contract.Ensures(CanWrite == false); + Contract.Ensures(CanSeek == false); + */ + + 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 abstract void Flush(); + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + public Task FlushAsync() + { + return FlushAsync(CancellationToken.None); + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + 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() + { + Contract.Ensures(Contract.Result<WaitHandle>() != null); + return new ManualResetEvent(false); + } + + [HostProtection(ExternalThreading=true)] + public virtual IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) + { + Contract.Ensures(Contract.Result<IAsyncResult>() != null); + return BeginReadInternal(buffer, offset, count, callback, state, serializeAsynchronously: false, apm: true); + } + + [HostProtection(ExternalThreading = true)] + internal IAsyncResult BeginReadInternal( + byte[] buffer, int offset, int count, AsyncCallback callback, Object state, + bool serializeAsynchronously, bool apm) + { + Contract.Ensures(Contract.Result<IAsyncResult>() != null); + if (!CanRead) __Error.ReadNotSupported(); + + // 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. + var 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; + Contract.Assert(thisTask != null, "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask"); + + 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("asyncResult"); + Contract.Ensures(Contract.Result<int>() >= 0); + Contract.EndContractBlock(); + + var readTask = _activeReadWriteTask; + + if (readTask == null) + { + throw new ArgumentException(Environment.GetResourceString("InvalidOperation_WrongAsyncResultOrEndReadCalledMultiple")); + } + else if (readTask != asyncResult) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WrongAsyncResultOrEndReadCalledMultiple")); + } + else if (!readTask._isRead) + { + throw new ArgumentException(Environment.GetResourceString("InvalidOperation_WrongAsyncResultOrEndReadCalledMultiple")); + } + + try + { + return readTask.GetAwaiter().GetResult(); // block until completion, then get result / propagate any exception + } + finally + { + FinishTrackingAsyncOperation(); + } + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public Task<int> ReadAsync(Byte[] buffer, int offset, int count) + { + return ReadAsync(buffer, offset, count, CancellationToken.None); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [System.Security.SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern bool HasOverriddenBeginEndRead(); + + private Task<Int32> BeginEndReadAsync(Byte[] buffer, Int32 offset, Int32 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<Int32>)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<Int32>.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; + } + + + + [HostProtection(ExternalThreading=true)] + public virtual IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) + { + Contract.Ensures(Contract.Result<IAsyncResult>() != null); + return BeginWriteInternal(buffer, offset, count, callback, state, serializeAsynchronously: false, apm: true); + } + + [HostProtection(ExternalThreading = true)] + internal IAsyncResult BeginWriteInternal( + byte[] buffer, int offset, int count, AsyncCallback callback, Object state, + bool serializeAsynchronously, bool apm) + { + Contract.Ensures(Contract.Result<IAsyncResult>() != null); + if (!CanWrite) __Error.WriteNotSupported(); + + // 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. + var 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; + Contract.Assert(thisTask != null, "Inside ReadWriteTask, InternalCurrent should be the ReadWriteTask"); + + 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) + { + Contract.Assert(readWriteTask != null); // Should be Contract.Requires, but CCRewrite is doing a poor job with + // preconditions in async methods that await. + Contract.Assert(asyncWaiter != null); // Ditto + + // If the wait has already completed, run the task. + if (asyncWaiter.IsCompleted) + { + Contract.Assert(asyncWaiter.IsRanToCompletion, "The semaphore wait should always complete successfully."); + RunReadWriteTask(readWriteTask); + } + else // Otherwise, wait for our turn, and then run the task. + { + asyncWaiter.ContinueWith((t, state) => { + Contract.Assert(t.IsRanToCompletion, "The semaphore wait should always complete successfully."); + var rwt = (ReadWriteTask)state; + rwt._stream.RunReadWriteTask(rwt); // RunReadWriteTask(readWriteTask); + }, readWriteTask, default(CancellationToken), TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + } + + private void RunReadWriteTask(ReadWriteTask readWriteTask) + { + Contract.Requires(readWriteTask != null); + Contract.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; + Contract.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("asyncResult"); + Contract.EndContractBlock(); + + var writeTask = _activeReadWriteTask; + if (writeTask == null) + { + throw new ArgumentException(Environment.GetResourceString("InvalidOperation_WrongAsyncResultOrEndWriteCalledMultiple")); + } + else if (writeTask != asyncResult) + { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_WrongAsyncResultOrEndWriteCalledMultiple")); + } + else if (writeTask._isRead) + { + throw new ArgumentException(Environment.GetResourceString("InvalidOperation_WrongAsyncResultOrEndWriteCalledMultiple")); + } + + try + { + writeTask.GetAwaiter().GetResult(); // block until completion, then propagate any exceptions + Contract.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; + } + + [SecuritySafeCritical] // necessary for EC.Capture + [MethodImpl(MethodImplOptions.NoInlining)] + 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) + { + Contract.Requires(function != null); + Contract.Requires(stream != null); + Contract.Requires(buffer != null); + Contract.EndContractBlock(); + + StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller; + + // 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(ref stackMark, + ExecutionContext.CaptureOptions.OptimizeDefaultCase | ExecutionContext.CaptureOptions.IgnoreSyncCtx); + base.AddCompletionAction(this); + } + } + + [SecurityCritical] // necessary for CoreCLR + private static void InvokeAsyncCallback(object completedTask) + { + var rwc = (ReadWriteTask)completedTask; + var callback = rwc._callback; + rwc._callback = null; + callback(rwc); + } + + [SecurityCritical] // necessary for CoreCLR + private static ContextCallback s_invokeAsyncCallback; + + [SecuritySafeCritical] // necessary for ExecutionContext.Run + 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. + var context = _context; + if (context == null) + { + var callback = _callback; + _callback = null; + callback(completingTask); + } + else + { + _context = null; + + var invokeAsyncCallback = s_invokeAsyncCallback; + if (invokeAsyncCallback == null) s_invokeAsyncCallback = invokeAsyncCallback = InvokeAsyncCallback; // benign race condition + + using(context) ExecutionContext.Run(context, invokeAsyncCallback, this, true); + } + } + + bool ITaskCompletionAction.InvokeMayRunArbitraryCode { get { return true; } } + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public Task WriteAsync(Byte[] buffer, int offset, int count) + { + return WriteAsync(buffer, offset, count, CancellationToken.None); + } + + + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [System.Security.SecuritySafeCritical] + [MethodImplAttribute(MethodImplOptions.InternalCall)] + private extern bool HasOverriddenBeginEndWrite(); + + private Task BeginEndWriteAsync(Byte[] buffer, Int32 offset, Int32 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(VoidTaskResult); + }); + } + + public abstract long Seek(long offset, SeekOrigin origin); + + public abstract void SetLength(long value); + + public abstract int Read([In, Out] byte[] buffer, int offset, int count); + + // 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() + { + Contract.Ensures(Contract.Result<int>() >= -1); + Contract.Ensures(Contract.Result<int>() < 256); + + 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); + + // 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); + } + + [HostProtection(Synchronization=true)] + public static Stream Synchronized(Stream stream) + { + if (stream==null) + throw new ArgumentNullException("stream"); + Contract.Ensures(Contract.Result<Stream>() != null); + Contract.EndContractBlock(); + 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) + { + Contract.Ensures(Contract.Result<IAsyncResult>() != null); + + // 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); + } + + if (callback != null) { + callback(asyncResult); + } + + return asyncResult; + } + + internal static int BlockingEndRead(IAsyncResult asyncResult) + { + Contract.Ensures(Contract.Result<int>() >= 0); + + return SynchronousAsyncResult.EndRead(asyncResult); + } + + internal IAsyncResult BlockingBeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) + { + Contract.Ensures(Contract.Result<IAsyncResult>() != null); + + // 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); + } + + if (callback != null) { + callback(asyncResult); + } + + return asyncResult; + } + + internal static void BlockingEndWrite(IAsyncResult asyncResult) + { + SynchronousAsyncResult.EndWrite(asyncResult); + } + + internal void ValidateCopyToArguments(Stream destination, int bufferSize) + { + if (destination == null) + throw new ArgumentNullException("destination"); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + if (!CanRead && !CanWrite) + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_StreamClosed")); + if (!destination.CanRead && !destination.CanWrite) + throw new ObjectDisposedException("destination", Environment.GetResourceString("ObjectDisposed_StreamClosed")); + if (!CanRead) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnreadableStream")); + if (!destination.CanWrite) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnwritableStream")); + Contract.EndContractBlock(); + } + + [Serializable] + private sealed class NullStream : Stream + { + internal NullStream() {} + + public override bool CanRead { + [Pure] + get { return true; } + } + + public override bool CanWrite { + [Pure] + get { return true; } + } + + public override bool CanSeek { + [Pure] + get { return true; } + } + + public override long Length { + get { return 0; } + } + + public override long Position { + get { return 0; } + set {} + } + + 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). + ValidateCopyToArguments(destination, 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() + { + } + + [ComVisible(false)] + public override Task FlushAsync(CancellationToken cancellationToken) + { + return cancellationToken.IsCancellationRequested ? + Task.FromCanceled(cancellationToken) : + Task.CompletedTask; + } + + [HostProtection(ExternalThreading = true)] + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) + { + if (!CanRead) __Error.ReadNotSupported(); + + return BlockingBeginRead(buffer, offset, count, callback, state); + } + + public override int EndRead(IAsyncResult asyncResult) + { + if (asyncResult == null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + return BlockingEndRead(asyncResult); + } + + [HostProtection(ExternalThreading = true)] + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) + { + if (!CanWrite) __Error.WriteNotSupported(); + + return BlockingBeginWrite(buffer, offset, count, callback, state); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + if (asyncResult == null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + BlockingEndWrite(asyncResult); + } + + public override int Read([In, Out] byte[] buffer, int offset, int count) + { + return 0; + } + + [ComVisible(false)] + public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + var nullReadTask = s_nullReadTask; + if (nullReadTask == null) + s_nullReadTask = nullReadTask = new Task<int>(false, 0, (TaskCreationOptions)InternalTaskOptions.DoNotDispose, CancellationToken.None); // benign race condition + return nullReadTask; + } + private static Task<int> s_nullReadTask; + + public override int ReadByte() + { + return -1; + } + + public override void Write(byte[] buffer, int offset, int count) + { + } + + [ComVisible(false)] + public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return cancellationToken.IsCancellationRequested ? + Task.FromCanceled(cancellationToken) : + Task.CompletedTask; + } + + 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> + internal sealed class SynchronousAsyncResult : IAsyncResult { + + private readonly Object _stateObject; + private readonly bool _isWrite; + private ManualResetEvent _waitHandle; + private ExceptionDispatchInfo _exceptionInfo; + + private bool _endXxxCalled; + private Int32 _bytesRead; + + internal SynchronousAsyncResult(Int32 bytesRead, Object asyncStateObject) { + _bytesRead = bytesRead; + _stateObject = asyncStateObject; + //_isWrite = false; + } + + 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 { + // We never hand out objects of this type to the user before the synchronous IO completed: + get { return true; } + } + + public WaitHandle AsyncWaitHandle { + get { + return LazyInitializer.EnsureInitialized(ref _waitHandle, () => new ManualResetEvent(true)); + } + } + + public Object AsyncState { + get { return _stateObject; } + } + + public bool CompletedSynchronously { + get { return true; } + } + + internal void ThrowIfError() { + if (_exceptionInfo != null) + _exceptionInfo.Throw(); + } + + internal static Int32 EndRead(IAsyncResult asyncResult) { + + SynchronousAsyncResult ar = asyncResult as SynchronousAsyncResult; + if (ar == null || ar._isWrite) + __Error.WrongAsyncResult(); + + if (ar._endXxxCalled) + __Error.EndReadCalledTwice(); + + ar._endXxxCalled = true; + + ar.ThrowIfError(); + return ar._bytesRead; + } + + internal static void EndWrite(IAsyncResult asyncResult) { + + SynchronousAsyncResult ar = asyncResult as SynchronousAsyncResult; + if (ar == null || !ar._isWrite) + __Error.WrongAsyncResult(); + + if (ar._endXxxCalled) + __Error.EndWriteCalledTwice(); + + 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. + [Serializable] + internal sealed class SyncStream : Stream, IDisposable + { + private Stream _stream; + + internal SyncStream(Stream stream) + { + if (stream == null) + throw new ArgumentNullException("stream"); + Contract.EndContractBlock(); + _stream = stream; + } + + public override bool CanRead { + [Pure] + get { return _stream.CanRead; } + } + + public override bool CanWrite { + [Pure] + get { return _stream.CanWrite; } + } + + public override bool CanSeek { + [Pure] + get { return _stream.CanSeek; } + } + + [ComVisible(false)] + public override bool CanTimeout { + [Pure] + get { + return _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; + } + } + } + + [ComVisible(false)] + public override int ReadTimeout { + get { + return _stream.ReadTimeout; + } + set { + _stream.ReadTimeout = value; + } + } + + [ComVisible(false)] + public override int WriteTimeout { + get { + return _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 void Flush() + { + lock(_stream) + _stream.Flush(); + } + + public override int Read([In, Out]byte[] bytes, int offset, int count) + { + lock(_stream) + return _stream.Read(bytes, offset, count); + } + + public override int ReadByte() + { + lock(_stream) + return _stream.ReadByte(); + } + + [HostProtection(ExternalThreading=true)] + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) + { + 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); + } + } + + public override int EndRead(IAsyncResult asyncResult) + { + if (asyncResult == null) + throw new ArgumentNullException("asyncResult"); + Contract.Ensures(Contract.Result<int>() >= 0); + Contract.EndContractBlock(); + + 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 WriteByte(byte b) + { + lock(_stream) + _stream.WriteByte(b); + } + + [HostProtection(ExternalThreading=true)] + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, Object state) + { + 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); + } + } + + public override void EndWrite(IAsyncResult asyncResult) + { + if (asyncResult == null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + lock(_stream) + _stream.EndWrite(asyncResult); + } + } + } +} diff --git a/src/mscorlib/src/System/IO/StreamReader.cs b/src/mscorlib/src/System/IO/StreamReader.cs new file mode 100644 index 0000000000..549733bb47 --- /dev/null +++ b/src/mscorlib/src/System/IO/StreamReader.cs @@ -0,0 +1,1293 @@ +// 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: For reading text from streams in a particular +** encoding. +** +** +===========================================================*/ + +using System; +using System.Text; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Security.Permissions; +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. + // + [Serializable] + [System.Runtime.InteropServices.ComVisible(true)] + public class StreamReader : TextReader + { + // StreamReader.Null is threadsafe. + public new static readonly StreamReader Null = new NullStreamReader(); + + // Encoding.GetPreamble() always allocates and returns a new byte[] array for + // encodings that have a preamble. + // We can avoid repeated allocations for the default and commonly used Encoding.UTF8 + // encoding by using our own private cached instance of the UTF8 preamble. + // This is lazily allocated the first time it is used. + private static byte[] s_utf8Preamble; + + // 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. + internal static int DefaultBufferSize + { + get + { + return 1024; + } + } + + private const int DefaultFileStreamBufferSize = 4096; + private const int MinBufferSize = 128; + private const int MaxSharedBuilderCapacity = 360; // also the max capacity used in StringBuilderCache + + private Stream stream; + private Encoding encoding; + private Decoder decoder; + private byte[] byteBuffer; + private char[] charBuffer; + private byte[] _preamble; // Encoding's preamble, which identifies this encoding. + 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; + + [NonSerialized] + private StringBuilder _builder; + + // 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; + + // 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 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. + [NonSerialized] + private volatile Task _asyncReadTask; + + 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. + + Task t = _asyncReadTask; + + if (t != null && !t.IsCompleted) + throw new InvalidOperationException(Environment.GetResourceString("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. + + internal StreamReader() { + } + + 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, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen) + { + if (stream == null || encoding == null) + throw new ArgumentNullException((stream == null ? "stream" : "encoding")); + if (!stream.CanRead) + throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotReadable")); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + Contract.EndContractBlock(); + + Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, 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) { + } + + [System.Security.SecuritySafeCritical] + public StreamReader(String path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize) + : this(path, encoding, detectEncodingFromByteOrderMarks, bufferSize, true) { + } + + [System.Security.SecurityCritical] + internal StreamReader(String path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool checkHost) + { + // Don't open a Stream before checking for invalid arguments, + // or we'll create a FileStream on disk and we won't close it until + // the finalizer runs, causing problems for applications. + if (path==null || encoding==null) + throw new ArgumentNullException((path==null ? "path" : "encoding")); + if (path.Length==0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + Contract.EndContractBlock(); + + Stream stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read, DefaultFileStreamBufferSize, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost); + Init(stream, encoding, detectEncodingFromByteOrderMarks, bufferSize, false); + } + + private void Init(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize, bool leaveOpen) { + this.stream = stream; + this.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; + + // Encoding.GetPreamble() always allocates and returns a new byte[] array for + // encodings that have a preamble. + // We can avoid repeated allocations for the default and commonly used Encoding.UTF8 + // encoding by using our own private cached instance of the UTF8 preamble. + // We specifically look for Encoding.UTF8 because we know it has a preamble, + // whereas other instances of UTF8Encoding may not have a preamble enabled, and + // there's no public way to tell if the preamble is enabled for an instance other + // than calling GetPreamble(), which we're trying to avoid. + // This means that other instances of UTF8Encoding are excluded from this optimization. + _preamble = object.ReferenceEquals(encoding, Encoding.UTF8) ? + (s_utf8Preamble ?? (s_utf8Preamble = encoding.GetPreamble())) : + encoding.GetPreamble(); + + _checkPreamble = (_preamble.Length > 0); + _isBlocked = false; + _closable = !leaveOpen; + } + + // Init used by NullStreamReader, to delay load encoding + internal void Init(Stream stream) + { + this.stream = stream; + _closable = true; + } + + public override void Close() + { + Dispose(true); + } + + protected override void Dispose(bool disposing) + { + // Dispose of our resources if this StreamReader is closable. + // Note that Console.In should be left open. + try { + // Note that Stream.Close() can potentially throw here. So we need to + // ensure cleaning up internal resources, inside the finally block. + if (!LeaveOpen && disposing && (stream != null)) + stream.Close(); + } + finally { + if (!LeaveOpen && (stream != null)) { + stream = null; + encoding = null; + decoder = null; + byteBuffer = null; + charBuffer = null; + charPos = 0; + charLen = 0; + _builder = null; + base.Dispose(disposing); + } + } + } + + public virtual Encoding CurrentEncoding { + get { return encoding; } + } + + public virtual Stream BaseStream { + get { return stream; } + } + + internal bool LeaveOpen { + get { return !_closable; } + } + + // 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 { + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + if (charPos < charLen) + return false; + + // This may block on pipes! + int numRead = ReadBuffer(); + return numRead == 0; + } + } + + [Pure] + public override int Peek() { + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + if (charPos == charLen) + { + if (_isBlocked || ReadBuffer() == 0) return -1; + } + return charBuffer[charPos]; + } + + public override int Read() { + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + if (charPos == charLen) { + if (ReadBuffer() == 0) return -1; + } + int result = charBuffer[charPos]; + charPos++; + return result; + } + + public override int Read([In, Out] char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (stream == null) + __Error.ReaderClosed(); + + 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; + while (count > 0) { + int n = charLen - charPos; + if (n == 0) n = ReadBuffer(buffer, index + charsRead, count, out readToUserBuffer); + if (n == 0) break; // We're at EOF + if (n > count) n = count; + if (!readToUserBuffer) { + Buffer.InternalBlockCopy(charBuffer, charPos * 2, buffer, (index + charsRead) * 2, n*2); + 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() + { + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + // Call ReadBuffer, then pull data out of charBuffer. + StringBuilder sb = AcquireSharedStringBuilder(charLen - charPos); + do { + sb.Append(charBuffer, charPos, charLen - charPos); + charPos = charLen; // Note we consumed these characters + ReadBuffer(); + } while (charLen > 0); + + return GetStringAndReleaseSharedStringBuilder(sb); + } + + public override int ReadBlock([In, Out] char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + return base.ReadBlock(buffer, index, count); + } + + // Trims n bytes from the front of the buffer. + private void CompressBuffer(int n) + { + Contract.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.InternalBlockCopy(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(true, 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(); + _maxCharsPerBuffer = encoding.GetMaxCharCount(byteBuffer.Length); + charBuffer = new char[_maxCharsPerBuffer]; + } + } + + // 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; + + Contract.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; + } + } + + Contract.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; + } + + private StringBuilder AcquireSharedStringBuilder(int capacity) + { + // Do not touch the shared builder if it will be removed on release + if (capacity > MaxSharedBuilderCapacity) + return new StringBuilder(capacity); + + // note that since StreamReader does not support concurrent reads it is not needed to + // set _builder to null to avoid parallel acquisitions. + StringBuilder sb = _builder; + + if (sb == null) + return _builder = new StringBuilder(capacity); + + // Clear the shared builder. Does not remove the allocated buffers so they are reused. + sb.Length = 0; + + // When needed, recreate the buffer backing the StringBuilder so that further Append calls + // are less likely to internally allocate new StringBuilders (or chunks). + if (sb.Capacity < capacity) + sb.Capacity = capacity; + + return sb; + } + + private string GetStringAndReleaseSharedStringBuilder(StringBuilder sb) + { + if (sb == _builder && sb.Capacity > MaxSharedBuilderCapacity) + _builder = null; + + return sb.ToString(); + } + + internal virtual int ReadBuffer() { + charLen = 0; + charPos = 0; + + if (!_checkPreamble) + byteLen = 0; + do { + if (_checkPreamble) { + Contract.Assert(bytePos <= _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); + Contract.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 { + Contract.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); + Contract.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 suppplied 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); + //Console.WriteLine("ReadBuffer called. chars: "+charLen); + 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(char[] userBuffer, int userOffset, int desiredChars, 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 = desiredChars >= _maxCharsPerBuffer; + + do { + Contract.Assert(charsRead == 0); + + if (_checkPreamble) { + Contract.Assert(bytePos <= _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); + Contract.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(byteBuffer, 0, byteLen, userBuffer, userOffset + charsRead); + 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 { + Contract.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); + + Contract.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 suppplied 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 = desiredChars >= _maxCharsPerBuffer; + } + + charPos = 0; + if (readToUserBuffer) { + charsRead += decoder.GetChars(byteBuffer, 0, byteLen, userBuffer, userOffset + charsRead); + 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 < desiredChars; + + //Console.WriteLine("ReadBuffer: charsRead: "+charsRead+" readToUserBuffer: "+readToUserBuffer); + 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() + { + if (stream == null) + __Error.ReaderClosed(); + + 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 = GetStringAndReleaseSharedStringBuilder(sb); + } + 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; + if (sb == null) sb = AcquireSharedStringBuilder(i + 80); + sb.Append(charBuffer, charPos, i); + } while (ReadBuffer() > 0); + return GetStringAndReleaseSharedStringBuilder(sb); + } + + #region Task based Async APIs + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + 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 overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read) when we are not sure. + if (this.GetType() != typeof(StreamReader)) + return base.ReadLineAsync(); + + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + Task<String> task = ReadLineAsyncInternal(); + _asyncReadTask = task; + + return task; + } + + private async Task<String> ReadLineAsyncInternal() + { + if (CharPos_Prop == CharLen_Prop && (await ReadBufferAsync().ConfigureAwait(false)) == 0) + return null; + + StringBuilder sb = null; + + do + { + char[] tmpCharBuffer = CharBuffer_Prop; + int tmpCharLen = CharLen_Prop; + int tmpCharPos = CharPos_Prop; + 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 = GetStringAndReleaseSharedStringBuilder(sb); + } + else + { + s = new String(tmpCharBuffer, tmpCharPos, i - tmpCharPos); + } + + CharPos_Prop = tmpCharPos = i + 1; + + if (ch == '\r' && (tmpCharPos < tmpCharLen || (await ReadBufferAsync().ConfigureAwait(false)) > 0)) + { + tmpCharPos = CharPos_Prop; + if (CharBuffer_Prop[tmpCharPos] == '\n') + CharPos_Prop = ++tmpCharPos; + } + + return s; + } + + i++; + + } while (i < tmpCharLen); + + i = tmpCharLen - tmpCharPos; + if (sb == null) sb = AcquireSharedStringBuilder(i + 80); + sb.Append(tmpCharBuffer, tmpCharPos, i); + + } while (await ReadBufferAsync().ConfigureAwait(false) > 0); + + return GetStringAndReleaseSharedStringBuilder(sb); + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + 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 overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read) when we are not sure. + if (this.GetType() != typeof(StreamReader)) + return base.ReadToEndAsync(); + + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + Task<String> task = ReadToEndAsyncInternal(); + _asyncReadTask = task; + + return task; + } + + private async Task<String> ReadToEndAsyncInternal() + { + // Call ReadBuffer, then pull data out of charBuffer. + StringBuilder sb = AcquireSharedStringBuilder(CharLen_Prop - CharPos_Prop); + do + { + int tmpCharPos = CharPos_Prop; + sb.Append(CharBuffer_Prop, tmpCharPos, CharLen_Prop - tmpCharPos); + CharPos_Prop = CharLen_Prop; // We consumed these characters + await ReadBufferAsync().ConfigureAwait(false); + } while (CharLen_Prop > 0); + + return GetStringAndReleaseSharedStringBuilder(sb); + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + public override Task<int> ReadAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Read() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read) when we are not sure. + if (this.GetType() != typeof(StreamReader)) + return base.ReadAsync(buffer, index, count); + + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + Task<int> task = ReadAsyncInternal(buffer, index, count); + _asyncReadTask = task; + + return task; + } + + internal override async Task<int> ReadAsyncInternal(char[] buffer, int index, int count) + { + if (CharPos_Prop == CharLen_Prop && (await ReadBufferAsync().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_Prop; + Stream tmpStream = Stream_Prop; + + while (count > 0) + { + // n is the characters available in _charBuffer + int n = CharLen_Prop - CharPos_Prop; + + // charBuffer is empty, let's read from the stream + if (n == 0) + { + CharLen_Prop = 0; + CharPos_Prop = 0; + + if (!CheckPreamble_Prop) + ByteLen_Prop = 0; + + readToUserBuffer = count >= MaxCharsPerBuffer_Prop; + + // 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 + { + Contract.Assert(n == 0); + + if (CheckPreamble_Prop) + { + Contract.Assert(BytePos_Prop <= Preamble_Prop.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); + int tmpBytePos = BytePos_Prop; + int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos).ConfigureAwait(false); + Contract.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_Prop > 0) + { + if (readToUserBuffer) + { + n = Decoder_Prop.GetChars(tmpByteBuffer, 0, ByteLen_Prop, buffer, index + charsRead); + CharLen_Prop = 0; // StreamReader's buffer is empty. + } + else + { + n = Decoder_Prop.GetChars(tmpByteBuffer, 0, ByteLen_Prop, CharBuffer_Prop, 0); + CharLen_Prop += n; // Number of chars in StreamReader's buffer. + } + } + + // How can part of the preamble yield any chars? + Contract.Assert(n == 0); + + IsBlocked_Prop = true; + break; + } + else + { + ByteLen_Prop += len; + } + } + else + { + Contract.Assert(BytePos_Prop == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); + + ByteLen_Prop = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length).ConfigureAwait(false); + + Contract.Assert(ByteLen_Prop >= 0, "Stream.Read returned a negative number! This is a bug in your stream class."); + + if (ByteLen_Prop == 0) // EOF + { + IsBlocked_Prop = 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_Prop = (ByteLen_Prop < tmpByteBuffer.Length); + + // Check for preamble before detect encoding. This is not to override the + // user suppplied 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_Prop && ByteLen_Prop >= 2) + { + DetectEncoding(); + // DetectEncoding changes some buffer state. Recompute this. + readToUserBuffer = count >= MaxCharsPerBuffer_Prop; + } + + Contract.Assert(n == 0); + + CharPos_Prop = 0; + if (readToUserBuffer) + { + n += Decoder_Prop.GetChars(tmpByteBuffer, 0, ByteLen_Prop, buffer, index + charsRead); + + // Why did the bytes yield no chars? + Contract.Assert(n > 0); + + CharLen_Prop = 0; // StreamReader's buffer is empty. + } + else + { + n = Decoder_Prop.GetChars(tmpByteBuffer, 0, ByteLen_Prop, CharBuffer_Prop, 0); + + // Why did the bytes yield no chars? + Contract.Assert(n > 0); + + CharLen_Prop += 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) + { + Buffer.InternalBlockCopy(CharBuffer_Prop, CharPos_Prop * 2, buffer, (index + charsRead) * 2, n * 2); + CharPos_Prop += 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_Prop) + break; + } // while (count > 0) + + return charsRead; + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Read() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read) when we are not sure. + if (this.GetType() != typeof(StreamReader)) + return base.ReadBlockAsync(buffer, index, count); + + if (stream == null) + __Error.ReaderClosed(); + + CheckAsyncTaskInProgress(); + + Task<int> task = base.ReadBlockAsync(buffer, index, count); + _asyncReadTask = task; + + return task; + } + + #region Private properties for async method performance + // Access to instance fields of MarshalByRefObject-derived types requires special JIT helpers that check + // if the instance operated on is remote. This is optimised for fields on this but if a method is Async + // and is thus lifted to a state machine type, access will be slow. + // As a workaround, we either cache instance fields in locals or use properties to access such fields. + + private Int32 CharLen_Prop { + get { return charLen; } + set { charLen = value; } + } + + private Int32 CharPos_Prop { + get { return charPos; } + set { charPos = value; } + } + + private Int32 ByteLen_Prop { + get { return byteLen; } + set { byteLen = value; } + } + + private Int32 BytePos_Prop { + get { return bytePos; } + set { bytePos = value; } + } + + private Byte[] Preamble_Prop { + get { return _preamble; } + } + + private bool CheckPreamble_Prop { + get { return _checkPreamble; } + } + + private Decoder Decoder_Prop { + get { return decoder; } + } + + private bool DetectEncoding_Prop { + get { return _detectEncoding; } + } + + private Char[] CharBuffer_Prop { + get { return charBuffer; } + } + + private Byte[] ByteBuffer_Prop { + get { return byteBuffer; } + } + + private bool IsBlocked_Prop { + get { return _isBlocked; } + set { _isBlocked = value; } + } + + private Stream Stream_Prop { + get { return stream; } + } + + private Int32 MaxCharsPerBuffer_Prop { + get { return _maxCharsPerBuffer; } + } + #endregion Private properties for async method performance + private async Task<int> ReadBufferAsync() + { + CharLen_Prop = 0; + CharPos_Prop = 0; + Byte[] tmpByteBuffer = ByteBuffer_Prop; + Stream tmpStream = Stream_Prop; + + if (!CheckPreamble_Prop) + ByteLen_Prop = 0; + do { + if (CheckPreamble_Prop) { + Contract.Assert(BytePos_Prop <= Preamble_Prop.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?"); + int tmpBytePos = BytePos_Prop; + int len = await tmpStream.ReadAsync(tmpByteBuffer, tmpBytePos, tmpByteBuffer.Length - tmpBytePos).ConfigureAwait(false); + Contract.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_Prop > 0) + { + CharLen_Prop += Decoder_Prop.GetChars(tmpByteBuffer, 0, ByteLen_Prop, CharBuffer_Prop, CharLen_Prop); + // Need to zero out the _byteLen after we consume these bytes so that we don't keep infinitely hitting this code path + BytePos_Prop = 0; ByteLen_Prop = 0; + } + + return CharLen_Prop; + } + + ByteLen_Prop += len; + } + else { + Contract.Assert(BytePos_Prop == 0, "_bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?"); + ByteLen_Prop = await tmpStream.ReadAsync(tmpByteBuffer, 0, tmpByteBuffer.Length).ConfigureAwait(false); + Contract.Assert(ByteLen_Prop >= 0, "Stream.Read returned a negative number! Bug in stream class."); + + if (ByteLen_Prop == 0) // We're at EOF + return CharLen_Prop; + } + + // _isBlocked == whether we read fewer bytes than we asked for. + // Note we must check it here because CompressBuffer or + // DetectEncoding will change _byteLen. + IsBlocked_Prop = (ByteLen_Prop < tmpByteBuffer.Length); + + // Check for preamble before detect encoding. This is not to override the + // user suppplied 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_Prop && ByteLen_Prop >= 2) + DetectEncoding(); + + CharLen_Prop += Decoder_Prop.GetChars(tmpByteBuffer, 0, ByteLen_Prop, CharBuffer_Prop, CharLen_Prop); + } while (CharLen_Prop == 0); + + return CharLen_Prop; + } + #endregion + + // No data, class doesn't need to be serializable. + // Note this class is threadsafe. + private class NullStreamReader : StreamReader + { + // Instantiating Encoding causes unnecessary perf hit. + internal NullStreamReader() { + Init(Stream.Null); + } + + public override Stream BaseStream { + get { return Stream.Null; } + } + + public override Encoding CurrentEncoding { + get { return 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; + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + 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/src/mscorlib/src/System/IO/StreamWriter.cs b/src/mscorlib/src/System/IO/StreamWriter.cs new file mode 100644 index 0000000000..65613bb0a6 --- /dev/null +++ b/src/mscorlib/src/System/IO/StreamWriter.cs @@ -0,0 +1,866 @@ +// 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: For writing text to streams in a particular +** encoding. +** +** +===========================================================*/ +using System; +using System.Text; +using System.Threading; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; +using System.Security.Permissions; +using System.Runtime.Serialization; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; +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. + // + [Serializable] + [ComVisible(true)] + 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. + internal const int DefaultBufferSize = 1024; // char[] + private const int DefaultFileStreamBufferSize = 4096; + private const int MinBufferSize = 128; + + private const Int32 DontCopyOnWriteLineThreshold = 512; + + // Bit bucket - Null has no backing store. Non closable. + public new static readonly StreamWriter Null = new StreamWriter(Stream.Null, new UTF8Encoding(false, true), MinBufferSize, true); + + private Stream stream; + private Encoding encoding; + private Encoder encoder; + private byte[] byteBuffer; + private char[] charBuffer; + private int charPos; + private int charLen; + private bool autoFlush; + private bool haveWrittenPreamble; + private bool closable; + +#if MDA_SUPPORTED + [NonSerialized] + // For StreamWriterBufferedDataLost MDA + private MdaHelper mdaHelper; +#endif + + // 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. + [NonSerialized] + private volatile Task _asyncWriteTask; + + 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. + + Task t = _asyncWriteTask; + + if (t != null && !t.IsCompleted) + throw new InvalidOperationException(Environment.GetResourceString("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 volatile Encoding _UTF8NoBOM; + + internal static Encoding UTF8NoBOM { + [FriendAccessAllowed] + get { + if (_UTF8NoBOM == null) { + // No need for double lock - we just want to avoid extra + // allocations in the common case. + UTF8Encoding noBOM = new UTF8Encoding(false, true); + Thread.MemoryBarrier(); + _UTF8NoBOM = noBOM; + } + return _UTF8NoBOM; + } + } + + + internal StreamWriter(): base(null) { // Ask for CurrentCulture all the time + } + + 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, int bufferSize, bool leaveOpen) + : base(null) // Ask for CurrentCulture all the time + { + if (stream == null || encoding == null) + throw new ArgumentNullException((stream == null ? "stream" : "encoding")); + if (!stream.CanWrite) + throw new ArgumentException(Environment.GetResourceString("Argument_StreamNotWritable")); + if (bufferSize <= 0) throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + Contract.EndContractBlock(); + + Init(stream, encoding, bufferSize, 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) { + } + + [System.Security.SecuritySafeCritical] + public StreamWriter(String path, bool append, Encoding encoding, int bufferSize): this(path, append, encoding, bufferSize, true) { + } + + [System.Security.SecurityCritical] + internal StreamWriter(String path, bool append, Encoding encoding, int bufferSize, bool checkHost) + : base(null) + { // Ask for CurrentCulture all the time + if (path == null) + throw new ArgumentNullException("path"); + if (encoding == null) + throw new ArgumentNullException("encoding"); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + if (bufferSize <= 0) throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + Contract.EndContractBlock(); + + Stream stream = CreateFile(path, append, checkHost); + Init(stream, encoding, bufferSize, false); + } + + [System.Security.SecuritySafeCritical] + private void Init(Stream streamArg, Encoding encodingArg, int bufferSize, bool shouldLeaveOpen) + { + this.stream = streamArg; + this.encoding = encodingArg; + this.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 = !shouldLeaveOpen; +#if MDA_SUPPORTED + if (Mda.StreamWriterBufferedDataLost.Enabled) { + String callstack = null; + if (Mda.StreamWriterBufferedDataLost.CaptureAllocatedCallStack) + callstack = Environment.GetStackTrace(null, false); + mdaHelper = new MdaHelper(this, callstack); + } +#endif + } + + [System.Security.SecurityCritical] + private static Stream CreateFile(String path, bool append, bool checkHost) { + FileMode mode = append? FileMode.Append: FileMode.Create; + FileStream f = new FileStream(path, mode, FileAccess.Write, FileShare.Read, + DefaultFileStreamBufferSize, FileOptions.SequentialScan, Path.GetFileName(path), false, false, checkHost); + return f; + } + + 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 (stream != null) { + // Note: flush on the underlying stream can throw (ex., low disk space) +#if FEATURE_CORECLR + if (disposing) +#else + if (disposing || (LeaveOpen && stream is __ConsoleStream)) +#endif + { + CheckAsyncTaskInProgress(); + + Flush(true, true); +#if MDA_SUPPORTED + // Disable buffered data loss mda + if (mdaHelper != null) + GC.SuppressFinalize(mdaHelper); +#endif + } + } + } + finally { + // Dispose of our resources if this StreamWriter is closable. + // Note: Console.Out and other such non closable streamwriters should be left alone + if (!LeaveOpen && stream != null) { + 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 { + stream = null; + byteBuffer = null; + charBuffer = null; + encoding = null; + encoder = null; + charLen = 0; + base.Dispose(disposing); + } + } + } + } + + 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. + if (stream == null) + __Error.WriterClosed(); + + // Perf boost for Flush on non-dirty writers. + if (charPos==0 && (!flushStream && !flushEncoder)) + return; + + if (!haveWrittenPreamble) { + haveWrittenPreamble = true; + byte[] preamble = encoding.GetPreamble(); + if (preamble.Length > 0) + stream.Write(preamble, 0, preamble.Length); + } + + 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 { return autoFlush; } + + set + { + CheckAsyncTaskInProgress(); + + autoFlush = value; + if (value) Flush(true, false); + } + } + + public virtual Stream BaseStream { + get { return stream; } + } + + internal bool LeaveOpen { + get { return !closable; } + } + + internal bool HaveWrittenPreamble { + set { haveWrittenPreamble= value; } + } + + public override Encoding Encoding { + get { return encoding; } + } + + public override void Write(char value) + { + CheckAsyncTaskInProgress(); + + if (charPos == charLen) Flush(false, false); + charBuffer[charPos] = value; + charPos++; + if (autoFlush) Flush(true, false); + } + + public override void Write(char[] buffer) + { + // This may be faster than the one with the index & count since it + // has to do less argument checking. + if (buffer==null) + return; + + CheckAsyncTaskInProgress(); + + int index = 0; + int count = buffer.Length; + while (count > 0) { + if (charPos == charLen) Flush(false, false); + int n = charLen - charPos; + if (n > count) n = count; + Contract.Assert(n > 0, "StreamWriter::Write(char[]) isn't making progress! This is most likely a race condition in user code."); + Buffer.InternalBlockCopy(buffer, index * sizeof(char), charBuffer, charPos * sizeof(char), n * sizeof(char)); + charPos += n; + index += n; + count -= n; + } + if (autoFlush) Flush(true, false); + } + + public override void Write(char[] buffer, int index, int count) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + CheckAsyncTaskInProgress(); + + while (count > 0) { + if (charPos == charLen) Flush(false, false); + int n = charLen - charPos; + if (n > count) n = count; + Contract.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code."); + Buffer.InternalBlockCopy(buffer, index * sizeof(char), charBuffer, charPos * sizeof(char), n * sizeof(char)); + charPos += n; + index += n; + count -= n; + } + if (autoFlush) Flush(true, false); + } + + public override void Write(String value) + { + if (value != null) + { + + CheckAsyncTaskInProgress(); + + int count = value.Length; + int index = 0; + while (count > 0) { + if (charPos == charLen) Flush(false, false); + int n = charLen - charPos; + if (n > count) n = count; + Contract.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 (autoFlush) Flush(true, false); + } + } + + #region Task based Async APIs + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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 overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write) when we are not sure. + if (this.GetType() != typeof(StreamWriter)) + return base.WriteAsync(value); + + if (stream == null) + __Error.WriterClosed(); + + 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, Int32 charPos, Int32 charLen, Char[] coreNewLine, + bool autoFlush, bool appendNewLine) + { + if (charPos == charLen) { + await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + charBuffer[charPos] = value; + charPos++; + + if (appendNewLine) + { + for (Int32 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); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + charBuffer[charPos] = coreNewLine[i]; + charPos++; + } + } + + if (autoFlush) { + await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + _this.CharPos_Prop = charPos; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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 overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write) when we are not sure. + if (this.GetType() != typeof(StreamWriter)) + return base.WriteAsync(value); + + if (value != null) + { + if (stream == null) + __Error.WriterClosed(); + + 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, Int32 charPos, Int32 charLen, Char[] coreNewLine, + bool autoFlush, bool appendNewLine) + { + Contract.Requires(value != null); + + int count = value.Length; + int index = 0; + + while (count > 0) + { + if (charPos == charLen) { + await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + int n = charLen - charPos; + if (n > count) + n = count; + + Contract.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 (Int32 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); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + charBuffer[charPos] = coreNewLine[i]; + charPos++; + } + } + + if (autoFlush) { + await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + _this.CharPos_Prop = charPos; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Write() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write) when we are not sure. + if (this.GetType() != typeof(StreamWriter)) + return base.WriteAsync(buffer, index, count); + + if (stream == null) + __Error.WriterClosed(); + + CheckAsyncTaskInProgress(); + + Task task = WriteAsyncInternal(this, buffer, index, count, 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[] buffer, Int32 index, Int32 count, + Char[] charBuffer, Int32 charPos, Int32 charLen, Char[] coreNewLine, + bool autoFlush, bool appendNewLine) + { + Contract.Requires(count == 0 || (count > 0 && buffer != null)); + Contract.Requires(index >= 0); + Contract.Requires(count >= 0); + Contract.Requires(buffer == null || (buffer != null && buffer.Length - index >= count)); + + while (count > 0) + { + if (charPos == charLen) { + await _this.FlushAsyncInternal(false, false, charBuffer, charPos).ConfigureAwait(false); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + int n = charLen - charPos; + if (n > count) n = count; + + Contract.Assert(n > 0, "StreamWriter::Write(char[], int, int) isn't making progress! This is most likely a race condition in user code."); + + Buffer.InternalBlockCopy(buffer, index * sizeof(char), charBuffer, charPos * sizeof(char), n * sizeof(char)); + + charPos += n; + index += n; + count -= n; + } + + if (appendNewLine) + { + for (Int32 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); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + charBuffer[charPos] = coreNewLine[i]; + charPos++; + } + } + + if (autoFlush) { + await _this.FlushAsyncInternal(true, false, charBuffer, charPos).ConfigureAwait(false); + Contract.Assert(_this.charPos == 0); + charPos = 0; + } + + _this.CharPos_Prop = charPos; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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 overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write) when we are not sure. + if (this.GetType() != typeof(StreamWriter)) + return base.WriteLineAsync(); + + if (stream == null) + __Error.WriterClosed(); + + CheckAsyncTaskInProgress(); + + Task task = WriteAsyncInternal(this, null, 0, 0, charBuffer, charPos, charLen, CoreNewLine, autoFlush, appendNewLine: true); + _asyncWriteTask = task; + + return task; + } + + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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 overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write) when we are not sure. + if (this.GetType() != typeof(StreamWriter)) + return base.WriteLineAsync(value); + + if (stream == null) + __Error.WriterClosed(); + + CheckAsyncTaskInProgress(); + + Task task = WriteAsyncInternal(this, value, charBuffer, charPos, charLen, CoreNewLine, autoFlush, appendNewLine: true); + _asyncWriteTask = task; + + return task; + } + + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteLineAsync(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 overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write) when we are not sure. + if (this.GetType() != typeof(StreamWriter)) + return base.WriteLineAsync(value); + + if (stream == null) + __Error.WriterClosed(); + + CheckAsyncTaskInProgress(); + + Task task = WriteAsyncInternal(this, value, charBuffer, charPos, charLen, CoreNewLine, autoFlush, appendNewLine: true); + _asyncWriteTask = task; + + return task; + } + + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Write() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write) when we are not sure. + if (this.GetType() != typeof(StreamWriter)) + return base.WriteLineAsync(buffer, index, count); + + if (stream == null) + __Error.WriterClosed(); + + CheckAsyncTaskInProgress(); + + Task task = WriteAsyncInternal(this, buffer, index, count, charBuffer, charPos, charLen, CoreNewLine, autoFlush, appendNewLine: true); + _asyncWriteTask = task; + + return task; + } + + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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 overriden. To be safe + // we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Flush) when we are not sure. + if (this.GetType() != typeof(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. + if (stream == null) + __Error.WriterClosed(); + + CheckAsyncTaskInProgress(); + + Task task = FlushAsyncInternal(true, true, charBuffer, charPos); + _asyncWriteTask = task; + + return task; + } + + private Int32 CharPos_Prop { + set { this.charPos = value; } + } + + private bool HaveWrittenPreamble_Prop { + set { this.haveWrittenPreamble = value; } + } + + private Task FlushAsyncInternal(bool flushStream, bool flushEncoder, + Char[] sCharBuffer, Int32 sCharPos) { + + // Perf boost for Flush on non-dirty writers. + if (sCharPos == 0 && !flushStream && !flushEncoder) + return Task.CompletedTask; + + Task flushTask = FlushAsyncInternal(this, flushStream, flushEncoder, sCharBuffer, sCharPos, this.haveWrittenPreamble, + this.encoding, this.encoder, this.byteBuffer, this.stream); + + this.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, Int32 charPos, bool haveWrittenPreamble, + Encoding encoding, Encoder encoder, Byte[] byteBuffer, Stream stream) + { + if (!haveWrittenPreamble) + { + _this.HaveWrittenPreamble_Prop = true; + byte[] preamble = encoding.GetPreamble(); + if (preamble.Length > 0) + await stream.WriteAsync(preamble, 0, preamble.Length).ConfigureAwait(false); + } + + int count = encoder.GetBytes(charBuffer, 0, charPos, byteBuffer, 0, flushEncoder); + if (count > 0) + await stream.WriteAsync(byteBuffer, 0, count).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().ConfigureAwait(false); + } + #endregion + +#if MDA_SUPPORTED + // StreamWriterBufferedDataLost MDA + // Instead of adding a finalizer to StreamWriter for detecting buffered data loss + // (ie, when the user forgets to call Close/Flush on the StreamWriter), we will + // have a separate object with normal finalization semantics that maintains a + // back pointer to this StreamWriter and alerts about any data loss + private sealed class MdaHelper + { + private StreamWriter streamWriter; + private String allocatedCallstack; // captures the callstack when this streamwriter was allocated + + internal MdaHelper(StreamWriter sw, String cs) + { + streamWriter = sw; + allocatedCallstack = cs; + } + + // Finalizer + ~MdaHelper() + { + // Make sure people closed this StreamWriter, exclude StreamWriter::Null. + if (streamWriter.charPos != 0 && streamWriter.stream != null && streamWriter.stream != Stream.Null) { + String fileName = (streamWriter.stream is FileStream) ? ((FileStream)streamWriter.stream).NameInternal : "<unknown>"; + String callStack = allocatedCallstack; + + if (callStack == null) + callStack = Environment.GetResourceString("IO_StreamWriterBufferedDataLostCaptureAllocatedFromCallstackNotEnabled"); + + String message = Environment.GetResourceString("IO_StreamWriterBufferedDataLost", streamWriter.stream.GetType().FullName, fileName, callStack); + + Mda.StreamWriterBufferedDataLost.ReportError(message); + } + } + } // class MdaHelper +#endif // MDA_SUPPORTED + + } // class StreamWriter +} // namespace diff --git a/src/mscorlib/src/System/IO/StringReader.cs b/src/mscorlib/src/System/IO/StringReader.cs new file mode 100644 index 0000000000..e5fa811453 --- /dev/null +++ b/src/mscorlib/src/System/IO/StringReader.cs @@ -0,0 +1,187 @@ +// 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: For reading text from strings +** +** +===========================================================*/ + +using System; +using System.Runtime.InteropServices; +using System.Diagnostics.Contracts; +using System.Security.Permissions; +using System.Threading.Tasks; + +namespace System.IO { + // This class implements a text reader that reads from a string. + // + [Serializable] + [ComVisible(true)] + public class StringReader : TextReader + { + private String _s; + private int _pos; + private int _length; + + public StringReader(String s) { + if (s == null) + throw new ArgumentNullException("s"); + Contract.EndContractBlock(); + _s = s; + _length = s == null? 0: s.Length; + } + + // Closes this StringReader. Following a call to this method, the String + // Reader will throw an ObjectDisposedException. + public override void Close() { + Dispose(true); + } + + protected override void Dispose(bool disposing) { + _s = null; + _pos = 0; + _length = 0; + base.Dispose(disposing); + } + + // Returns the next available character without actually reading it from + // the underlying string. The current position of the StringReader is not + // changed by this operation. The returned value is -1 if no further + // characters are available. + // + [Pure] + public override int Peek() { + if (_s == null) + __Error.ReaderClosed(); + if (_pos == _length) return -1; + return _s[_pos]; + } + + // Reads the next character from the underlying string. The returned value + // is -1 if no further characters are available. + // + public override int Read() { + if (_s == null) + __Error.ReaderClosed(); + if (_pos == _length) return -1; + return _s[_pos++]; + } + + // Reads a block of characters. This method will read up to count + // characters from this StringReader into the buffer character + // array starting at position index. Returns the actual number of + // characters read, or zero if the end of the string is reached. + // + public override int Read([In, Out] char[] buffer, int index, int count) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + if (_s == null) + __Error.ReaderClosed(); + + int n = _length - _pos; + if (n > 0) { + if (n > count) n = count; + _s.CopyTo(_pos, buffer, index, n); + _pos += n; + } + return n; + } + + public override String ReadToEnd() + { + if (_s == null) + __Error.ReaderClosed(); + String s; + if (_pos==0) + s = _s; + else + s = _s.Substring(_pos, _length - _pos); + _pos = _length; + return s; + } + + // 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 underlying string has been reached. + // + public override String ReadLine() { + if (_s == null) + __Error.ReaderClosed(); + int i = _pos; + while (i < _length) { + char ch = _s[i]; + if (ch == '\r' || ch == '\n') { + String result = _s.Substring(_pos, i - _pos); + _pos = i + 1; + if (ch == '\r' && _pos < _length && _s[_pos] == '\n') _pos++; + return result; + } + i++; + } + if (i > _pos) { + String result = _s.Substring(_pos, i - _pos); + _pos = i; + return result; + } + return null; + } + + #region Task based Async APIs + [ComVisible(false)] + public override Task<String> ReadLineAsync() + { + return Task.FromResult(ReadLine()); + } + + [ComVisible(false)] + public override Task<String> ReadToEndAsync() + { + return Task.FromResult(ReadToEnd()); + } + + [ComVisible(false)] + public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + Contract.EndContractBlock(); + + return Task.FromResult(ReadBlock(buffer, index, count)); + } + + [ComVisible(false)] + public override Task<int> ReadAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + return Task.FromResult(Read(buffer, index, count)); + } + #endregion + } +} diff --git a/src/mscorlib/src/System/IO/StringWriter.cs b/src/mscorlib/src/System/IO/StringWriter.cs new file mode 100644 index 0000000000..282a7910c2 --- /dev/null +++ b/src/mscorlib/src/System/IO/StringWriter.cs @@ -0,0 +1,196 @@ +// 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: For writing text to a string +** +** +===========================================================*/ + +using System; +using System.Runtime; +using System.Text; +using System.Globalization; +using System.Diagnostics.Contracts; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Threading.Tasks; + +namespace System.IO { + // This class implements a text writer that writes to a string buffer and allows + // the resulting sequence of characters to be presented as a string. + // + [Serializable] + [ComVisible(true)] + public class StringWriter : TextWriter + { + private static volatile UnicodeEncoding m_encoding=null; + + private StringBuilder _sb; + private bool _isOpen; + + // Constructs a new StringWriter. A new StringBuilder is automatically + // created and associated with the new StringWriter. + public StringWriter() + : this(new StringBuilder(), CultureInfo.CurrentCulture) + { + } + + public StringWriter(IFormatProvider formatProvider) + : this(new StringBuilder(), formatProvider) { + } + + // Constructs a new StringWriter that writes to the given StringBuilder. + // + public StringWriter(StringBuilder sb) : this(sb, CultureInfo.CurrentCulture) { + } + + public StringWriter(StringBuilder sb, IFormatProvider formatProvider) : base(formatProvider) { + if (sb==null) + throw new ArgumentNullException("sb", Environment.GetResourceString("ArgumentNull_Buffer")); + Contract.EndContractBlock(); + _sb = sb; + _isOpen = true; + } + + public override void Close() + { + Dispose(true); + } + + protected override void Dispose(bool disposing) + { + // Do not destroy _sb, so that we can extract this after we are + // done writing (similar to MemoryStream's GetBuffer & ToArray methods) + _isOpen = false; + base.Dispose(disposing); + } + + + public override Encoding Encoding { + get { + if (m_encoding==null) { + m_encoding = new UnicodeEncoding(false, false); + } + return m_encoding; + } + } + + // Returns the underlying StringBuilder. This is either the StringBuilder + // that was passed to the constructor, or the StringBuilder that was + // automatically created. + // + public virtual StringBuilder GetStringBuilder() { + return _sb; + } + + // Writes a character to the underlying string buffer. + // + public override void Write(char value) { + if (!_isOpen) + __Error.WriterClosed(); + _sb.Append(value); + } + + // Writes a range of a character array to the underlying string buffer. + // This method will write count characters of data into this + // StringWriter from the buffer character array starting at position + // index. + // + public override void Write(char[] buffer, int index, int count) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (!_isOpen) + __Error.WriterClosed(); + + _sb.Append(buffer, index, count); + } + + // Writes a string to the underlying string buffer. If the given string is + // null, nothing is written. + // + public override void Write(String value) { + if (!_isOpen) + __Error.WriterClosed(); + if (value != null) _sb.Append(value); + } + + + #region Task based Async APIs + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteAsync(char value) + { + Write(value); + return Task.CompletedTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteAsync(String value) + { + Write(value); + return Task.CompletedTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteAsync(char[] buffer, int index, int count) + { + Write(buffer, index, count); + return Task.CompletedTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteLineAsync(char value) + { + WriteLine(value); + return Task.CompletedTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteLineAsync(String value) + { + WriteLine(value); + return Task.CompletedTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + WriteLine(buffer, index, count); + return Task.CompletedTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task FlushAsync() + { + return Task.CompletedTask; + } + #endregion + + // Returns a string containing the characters written to this TextWriter + // so far. + // + public override String ToString() { + return _sb.ToString(); + } + } +} diff --git a/src/mscorlib/src/System/IO/TextReader.cs b/src/mscorlib/src/System/IO/TextReader.cs new file mode 100644 index 0000000000..ede482784a --- /dev/null +++ b/src/mscorlib/src/System/IO/TextReader.cs @@ -0,0 +1,413 @@ +// 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 Text-only Readers. +** Subclasses will include StreamReader & StringReader. +** +** +===========================================================*/ + +using System; +using System.Text; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Reflection; +using System.Security.Permissions; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Threading; +using System.Threading.Tasks; + +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. + [Serializable] + [ComVisible(true)] +#if FEATURE_REMOTING + public abstract class TextReader : MarshalByRefObject, IDisposable { +#else // FEATURE_REMOTING + public abstract class TextReader : IDisposable { +#endif // FEATURE_REMOTING + + public static readonly TextReader Null = new NullTextReader(); + + protected TextReader() {} + + // Closes this TextReader and releases any system resources associated with the + // TextReader. Following a call to Close, any operations on the TextReader + // may raise exceptions. + // + // This default method is empty, but descendant classes can override the + // method to provide the appropriate functionality. + 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. + // + [Pure] + public virtual int Peek() + { + Contract.Ensures(Contract.Result<int>() >= -1); + + 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() + { + Contract.Ensures(Contract.Result<int>() >= -1); + 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([In, Out] char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.Ensures(Contract.Result<int>() >= 0); + Contract.Ensures(Contract.Result<int>() <= Contract.OldValue(count)); + Contract.EndContractBlock(); + + int n = 0; + do { + int ch = Read(); + if (ch == -1) break; + buffer[index + n++] = (char)ch; + } while (n < count); + return n; + } + + // Reads all characters from the current position to the end of the + // TextReader, and returns them as one string. + public virtual String ReadToEnd() + { + Contract.Ensures(Contract.Result<String>() != null); + + 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([In, Out] char[] buffer, int index, int count) + { + Contract.Ensures(Contract.Result<int>() >= 0); + Contract.Ensures(Contract.Result<int>() <= count); + + int i, n = 0; + do { + n += (i = Read(buffer, index + n, count - n)); + } while (i > 0 && n < count); + return n; + } + + // 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 + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + public virtual Task<String> ReadLineAsync() + { + return Task<String>.Factory.StartNew(state => + { + return ((TextReader)state).ReadLine(); + }, + this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + public async virtual Task<String> ReadToEndAsync() + { + char[] chars = new char[4096]; + int len; + StringBuilder sb = new StringBuilder(4096); + while((len = await ReadAsyncInternal(chars, 0, chars.Length).ConfigureAwait(false)) != 0) + { + sb.Append(chars, 0, len); + } + return sb.ToString(); + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + public virtual Task<int> ReadAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + return ReadAsyncInternal(buffer, index, count); + } + + internal virtual Task<int> ReadAsyncInternal(char[] buffer, int index, int count) + { + Contract.Requires(buffer != null); + Contract.Requires(index >= 0); + Contract.Requires(count >= 0); + Contract.Requires(buffer.Length - index >= count); + + var tuple = new Tuple<TextReader, char[], int, int>(this, buffer, index, count); + return Task<int>.Factory.StartNew(state => + { + var t = (Tuple<TextReader, char[], int, int>)state; + return t.Item1.Read(t.Item2, t.Item3, t.Item4); + }, + tuple, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + public virtual Task<int> ReadBlockAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + Contract.EndContractBlock(); + + return ReadBlockAsyncInternal(buffer, index, count); + } + + [HostProtection(ExternalThreading=true)] + private async Task<int> ReadBlockAsyncInternal(char[] buffer, int index, int count) + { + Contract.Requires(buffer != null); + Contract.Requires(index >= 0); + Contract.Requires(count >= 0); + Contract.Requires(buffer.Length - index >= count); + + int i, n = 0; + do + { + i = await ReadAsyncInternal(buffer, index + n, count - n).ConfigureAwait(false); + n += i; + } while (i > 0 && n < count); + + return n; + } + #endregion + + [HostProtection(Synchronization=true)] + public static TextReader Synchronized(TextReader reader) + { + if (reader==null) + throw new ArgumentNullException("reader"); + Contract.Ensures(Contract.Result<TextReader>() != null); + Contract.EndContractBlock(); + + if (reader is SyncTextReader) + return reader; + + return new SyncTextReader(reader); + } + + [Serializable] + private sealed class NullTextReader : TextReader + { + public NullTextReader(){} + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + public override int Read(char[] buffer, int index, int count) + { + return 0; + } + + public override String ReadLine() + { + return null; + } + } + + [Serializable] + internal sealed class SyncTextReader : TextReader + { + internal TextReader _in; + + internal SyncTextReader(TextReader t) + { + _in = t; + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Close() + { + // So that any overriden Close() gets run + _in.Close(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + protected override void Dispose(bool disposing) + { + // Explicitly pick up a potentially methodimpl'ed Dispose + if (disposing) + ((IDisposable)_in).Dispose(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override int Peek() + { + return _in.Peek(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override int Read() + { + return _in.Read(); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override int Read([In, Out] char[] buffer, int index, int count) + { + return _in.Read(buffer, index, count); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override int ReadBlock([In, Out] char[] buffer, int index, int count) + { + return _in.ReadBlock(buffer, index, count); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override String ReadLine() + { + return _in.ReadLine(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override String ReadToEnd() + { + return _in.ReadToEnd(); + } + + // + // On SyncTextReader all APIs should run synchronously, even the async ones. + // + + [ComVisible(false)] + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override Task<String> ReadLineAsync() + { + return Task.FromResult(ReadLine()); + } + + [ComVisible(false)] + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override Task<String> ReadToEndAsync() + { + return Task.FromResult(ReadToEnd()); + } + + [ComVisible(false)] + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + + Contract.EndContractBlock(); + + return Task.FromResult(ReadBlock(buffer, index, count)); + } + + [ComVisible(false)] + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override Task<int> ReadAsync(char[] buffer, int index, int count) + { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0 || count < 0) + throw new ArgumentOutOfRangeException((index < 0 ? "index" : "count"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + return Task.FromResult(Read(buffer, index, count)); + } + } + } +} diff --git a/src/mscorlib/src/System/IO/TextWriter.cs b/src/mscorlib/src/System/IO/TextWriter.cs new file mode 100644 index 0000000000..165001e1a6 --- /dev/null +++ b/src/mscorlib/src/System/IO/TextWriter.cs @@ -0,0 +1,915 @@ +// 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 Text-only Writers. +** Subclasses will include StreamWriter & StringWriter. +** +** +===========================================================*/ + +using System; +using System.Text; +using System.Threading; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Reflection; +using System.Security.Permissions; +using System.Globalization; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; + +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. + [Serializable] + [ComVisible(true)] +#if FEATURE_REMOTING + public abstract class TextWriter : MarshalByRefObject, IDisposable { +#else // FEATURE_REMOTING + public abstract class TextWriter : IDisposable { +#endif // FEATURE_REMOTING + public static readonly TextWriter Null = new NullTextWriter(); + + // This should be initialized to Environment.NewLine, but + // to avoid loading Environment unnecessarily so I've duplicated + // the value here. +#if !PLATFORM_UNIX + private const String InitialNewLine = "\r\n"; + + protected char[] CoreNewLine = new char[] { '\r', '\n' }; +#else + private const String InitialNewLine = "\n"; + + protected char[] CoreNewLine = new char[] {'\n'}; +#endif // !PLATFORM_UNIX + + // Can be null - if so, ask for the Thread's CurrentCulture every time. + private 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 Thread.CurrentThread.CurrentCulture; + else + return InternalFormatProvider; + } + } + + // Closes this TextWriter and releases any system resources associated with the + // TextWriter. Following a call to Close, any operations on the TextWriter + // may raise exceptions. This default method is empty, but descendant + // classes can override the method to provide the appropriate + // functionality. + public virtual void Close() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + } + + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + // 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; + } + + // Returns the line terminator string used by this TextWriter. The default line + // terminator string is a carriage return followed by a line feed ("\r\n"). + // + // Sets the line terminator string for this TextWriter. 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". + // + public virtual String NewLine { + get { return new String(CoreNewLine); } + set { + if (value == null) + value = InitialNewLine; + CoreNewLine = value.ToCharArray(); + } + } + + + [HostProtection(Synchronization=true)] + public static TextWriter Synchronized(TextWriter writer) { + if (writer==null) + throw new ArgumentNullException("writer"); + Contract.Ensures(Contract.Result<TextWriter>() != null); + Contract.EndContractBlock(); + + if (writer is SyncTextWriter) + return writer; + + return new SyncTextWriter(writer); + } + + // 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("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (index < 0) + throw new ArgumentOutOfRangeException("index", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - index < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + for (int i = 0; i < count; i++) Write(buffer[index + i]); + } + + // Writes the text representation of a boolean to the text stream. This + // method outputs either Boolean.TrueString or Boolean.FalseString. + // + public virtual void Write(bool value) { + Write(value ? Boolean.TrueLiteral : Boolean.FalseLiteral); + } + + // Writes the text representation of an integer to the text stream. The + // text representation of the given value is produced by calling the + // Int32.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 + // UInt32.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 + // Int64.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 UInt64.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) { + IFormattable f = value as IFormattable; + if (f != null) + Write(f.ToString(null, FormatProvider)); + else + Write(value.ToString()); + } + } + +#if false + // // Converts the wchar * to a string and writes this to the stream. + // // + // __attribute NonCLSCompliantAttribute() + // public void Write(wchar *value) { + // Write(new String(value)); + // } + + // // Treats the byte* as a LPCSTR, converts it to a string, and writes it to the stream. + // // + // __attribute NonCLSCompliantAttribute() + // public void Write(byte *value) { + // Write(new String(value)); + // } +#endif + + + // 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 a carriage return followed by a line feed ("\r\n"), 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(); + } + + // 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) { + WriteLine(); + } + else { + // We'd ideally like WriteLine to be atomic, in that one call + // to WriteLine equals one call to the OS (ie, so writing to + // console while simultaneously calling printf will guarantee we + // write out a string and new line chars, without any interference). + // Additionally, we need to call ToCharArray on Strings anyways, + // so allocating a char[] here isn't any worse than what we were + // doing anyways. We do reduce the number of calls to the + // backing store this way, potentially. + int vLen = value.Length; + int nlLen = CoreNewLine.Length; + char[] chars = new char[vLen+nlLen]; + value.CopyTo(0, chars, 0, vLen); + // CoreNewLine will almost always be 2 chars, and possibly 1. + if (nlLen == 2) { + chars[vLen] = CoreNewLine[0]; + chars[vLen+1] = CoreNewLine[1]; + } + else if (nlLen == 1) + chars[vLen] = CoreNewLine[0]; + else + Buffer.InternalBlockCopy(CoreNewLine, 0, chars, vLen * 2, nlLen * 2); + Write(chars, 0, vLen + nlLen); + } + /* + Write(value); // We could call Write(String) on StreamWriter... + 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. + IFormattable f = value as IFormattable; + if (f != null) + 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 + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public Task WriteAsync(char[] buffer) + { + if (buffer == null) return Task.CompletedTask; + return WriteAsync(buffer, 0, buffer.Length); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public Task WriteLineAsync(char[] buffer) + { + if (buffer == null) return Task.CompletedTask; + return WriteLineAsync(buffer, 0, buffer.Length); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + 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); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public virtual Task WriteLineAsync() + { + return WriteAsync(CoreNewLine); + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public virtual Task FlushAsync() + { + return Task.Factory.StartNew(state => + { + ((TextWriter)state).Flush(); + }, + this, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default); + } + #endregion + + [Serializable] + private sealed class NullTextWriter : TextWriter + { + internal NullTextWriter(): base(CultureInfo.InvariantCulture) { + } + + public override Encoding Encoding { + get { return Encoding.Default; } + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + 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) { + } + } + + [Serializable] + internal sealed class SyncTextWriter : TextWriter, IDisposable + { + private TextWriter _out; + + internal SyncTextWriter(TextWriter t): base(t.FormatProvider) { + _out = t; + } + + public override Encoding Encoding { + get { return _out.Encoding; } + } + + public override IFormatProvider FormatProvider { + get { return _out.FormatProvider; } + } + + public override String NewLine { + [MethodImplAttribute(MethodImplOptions.Synchronized)] + get { return _out.NewLine; } + [MethodImplAttribute(MethodImplOptions.Synchronized)] + set { _out.NewLine = value; } + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Close() { + // So that any overriden Close() gets run + _out.Close(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + protected override void Dispose(bool disposing) { + // Explicitly pick up a potentially methodimpl'ed Dispose + if (disposing) + ((IDisposable)_out).Dispose(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Flush() { + _out.Flush(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(char value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(char[] buffer) { + _out.Write(buffer); + } + + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(char[] buffer, int index, int count) { + _out.Write(buffer, index, count); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(bool value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(int value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(uint value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(long value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(ulong value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(float value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(double value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(Decimal value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(String value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(Object value) { + _out.Write(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(String format, Object arg0) { + _out.Write(format, arg0); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(String format, Object arg0, Object arg1) { + _out.Write(format, arg0, arg1); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(String format, Object arg0, Object arg1, Object arg2) { + _out.Write(format, arg0, arg1, arg2); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void Write(String format, Object[] arg) { + _out.Write(format, arg); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine() { + _out.WriteLine(); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(char value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(decimal value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(char[] buffer) { + _out.WriteLine(buffer); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(char[] buffer, int index, int count) { + _out.WriteLine(buffer, index, count); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(bool value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(int value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(uint value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(long value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(ulong value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(float value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(double value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(String value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(Object value) { + _out.WriteLine(value); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(String format, Object arg0) { + _out.WriteLine(format, arg0); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(String format, Object arg0, Object arg1) { + _out.WriteLine(format, arg0, arg1); + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + public override void WriteLine(String format, Object arg0, Object arg1, Object arg2) { + _out.WriteLine(format, arg0, arg1, arg2); + } + + [MethodImplAttribute(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. + // + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + [ComVisible(false)] + public override Task WriteAsync(char value) + { + Write(value); + return Task.CompletedTask; + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + [ComVisible(false)] + public override Task WriteAsync(String value) + { + Write(value); + return Task.CompletedTask; + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + [ComVisible(false)] + public override Task WriteAsync(char[] buffer, int index, int count) + { + Write(buffer, index, count); + return Task.CompletedTask; + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + [ComVisible(false)] + public override Task WriteLineAsync(char value) + { + WriteLine(value); + return Task.CompletedTask; + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + [ComVisible(false)] + public override Task WriteLineAsync(String value) + { + WriteLine(value); + return Task.CompletedTask; + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + [ComVisible(false)] + public override Task WriteLineAsync(char[] buffer, int index, int count) + { + WriteLine(buffer, index, count); + return Task.CompletedTask; + } + + [MethodImplAttribute(MethodImplOptions.Synchronized)] + [ComVisible(false)] + public override Task FlushAsync() + { + Flush(); + return Task.CompletedTask; + } + } + } +} diff --git a/src/mscorlib/src/System/IO/UnmanagedMemoryAccessor.cs b/src/mscorlib/src/System/IO/UnmanagedMemoryAccessor.cs new file mode 100644 index 0000000000..ea70057217 --- /dev/null +++ b/src/mscorlib/src/System/IO/UnmanagedMemoryAccessor.cs @@ -0,0 +1,1176 @@ +// 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; +using System.Runtime.InteropServices; +using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; +using System.Runtime.Versioning; +using System.Security.Permissions; +using Microsoft.Win32.SafeHandles; +using System.Diagnostics.Contracts; + +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 { + + [System.Security.SecurityCritical] // auto-generated + private SafeBuffer _buffer; + private Int64 _offset; + [ContractPublicPropertyName("Capacity")] + private Int64 _capacity; + private FileAccess _access; + private bool _isOpen; + private bool _canRead; + private bool _canWrite; + + protected UnmanagedMemoryAccessor() { + _isOpen = false; + } + + #region SafeBuffer ctors and initializers + // <SecurityKernel Critical="True" Ring="1"> + // <ReferencesCritical Name="Method: Initialize(SafeBuffer, Int64, Int64, FileAccess):Void" Ring="1" /> + // </SecurityKernel> + [System.Security.SecuritySafeCritical] + public UnmanagedMemoryAccessor(SafeBuffer buffer, Int64 offset, Int64 capacity) { + Initialize(buffer, offset, capacity, FileAccess.Read); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public UnmanagedMemoryAccessor(SafeBuffer buffer, Int64 offset, Int64 capacity, FileAccess access) { + Initialize(buffer, offset, capacity, access); + } + + [System.Security.SecuritySafeCritical] // auto-generated +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] +#pragma warning restore 618 + protected void Initialize(SafeBuffer buffer, Int64 offset, Int64 capacity, FileAccess access) { + if (buffer == null) { + throw new ArgumentNullException("buffer"); + } + if (offset < 0) { + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (capacity < 0) { + throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (buffer.ByteLength < (UInt64)(offset + capacity)) { + throw new ArgumentException(Environment.GetResourceString("Argument_OffsetAndCapacityOutOfBounds")); + } + if (access < FileAccess.Read || access > FileAccess.ReadWrite) { + throw new ArgumentOutOfRangeException("access"); + } + Contract.EndContractBlock(); + + if (_isOpen) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CalledTwice")); + } + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + buffer.AcquirePointer(ref pointer); + if (((byte*)((Int64)pointer + offset + capacity)) < pointer) { + throw new ArgumentException(Environment.GetResourceString("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; + } + + #endregion + + public Int64 Capacity { + get { + return _capacity; + } + } + + public bool CanRead { + get { + return _isOpen && _canRead; + } + } + + public bool CanWrite { + get { + return _isOpen && _canWrite; + } + } + + protected virtual void Dispose(bool disposing) { + _isOpen = false; + } + + public void Dispose() { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected bool IsOpen { + get { return _isOpen; } + } + + public bool ReadBoolean(Int64 position) { + int sizeOfType = sizeof(bool); + EnsureSafeToRead(position, sizeOfType); + + byte b = InternalReadByte(position); + return b != 0; + } + + public byte ReadByte(Int64 position) { + int sizeOfType = sizeof(byte); + EnsureSafeToRead(position, sizeOfType); + + return InternalReadByte(position); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public char ReadChar(Int64 position) { + int sizeOfType = sizeof(char); + EnsureSafeToRead(position, sizeOfType); + + char result; + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((char*)(pointer)); +#if ALIGN_ACCESS + } + else { + result = (char)( *pointer | *(pointer + 1) << 8 ) ; + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + // See comment above. + [System.Security.SecuritySafeCritical] + public Int16 ReadInt16(Int64 position) { + int sizeOfType = sizeof(Int16); + EnsureSafeToRead(position, sizeOfType); + + Int16 result; + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((Int16*)(pointer)); +#if ALIGN_ACCESS + } + else { + result = (Int16)( *pointer | *(pointer + 1) << 8 ); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public Int32 ReadInt32(Int64 position) { + int sizeOfType = sizeof(Int32); + EnsureSafeToRead(position, sizeOfType); + + Int32 result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((Int32*)(pointer)); +#if ALIGN_ACCESS + } + else { + result = (Int32)( *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24 ); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public Int64 ReadInt64(Int64 position) { + int sizeOfType = sizeof(Int64); + EnsureSafeToRead(position, sizeOfType); + + Int64 result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((Int64*)(pointer)); +#if ALIGN_ACCESS + } + else { + int lo = *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24; + int hi = *(pointer + 4) | *(pointer + 5) << 8 | *(pointer + 6) << 16 | *(pointer + 7) << 24; + result = (Int64)(((Int64)hi << 32) | (UInt32)lo); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public Decimal ReadDecimal(Int64 position) { + int sizeOfType = sizeof(Decimal); + EnsureSafeToRead(position, sizeOfType); + + int[] decimalArray = new int[4]; + ReadArray<int>(position, decimalArray, 0, decimalArray.Length); + + return new Decimal(decimalArray); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public Single ReadSingle(Int64 position) { + int sizeOfType = sizeof(Single); + EnsureSafeToRead(position, sizeOfType); + + Single result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((Single*)(pointer)); +#if ALIGN_ACCESS + } + else { + UInt32 tempResult = (UInt32)( *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24 ); + result = *((float*)&tempResult); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public Double ReadDouble(Int64 position) { + int sizeOfType = sizeof(Double); + EnsureSafeToRead(position, sizeOfType); + + Double result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((Double*)(pointer)); +#if ALIGN_ACCESS + } + else { + + UInt32 lo = (UInt32)( *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24 ); + UInt32 hi = (UInt32)( *(pointer + 4) | *(pointer + 5) << 8 | *(pointer + 6) << 16 | *(pointer + 7) << 24 ); + UInt64 tempResult = ((UInt64)hi) << 32 | lo; + result = *((double*)&tempResult); + + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public SByte ReadSByte(Int64 position) { + int sizeOfType = sizeof(SByte); + EnsureSafeToRead(position, sizeOfType); + + SByte result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + result = *((SByte*)pointer); + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public UInt16 ReadUInt16(Int64 position) { + int sizeOfType = sizeof(UInt16); + EnsureSafeToRead(position, sizeOfType); + + UInt16 result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((UInt16*)(pointer)); +#if ALIGN_ACCESS + } + else { + result = (UInt16)( *pointer | *(pointer + 1) << 8 ); + } +#endif + + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public UInt32 ReadUInt32(Int64 position) { + int sizeOfType = sizeof(UInt32); + EnsureSafeToRead(position, sizeOfType); + + UInt32 result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((UInt32*)(pointer)); +#if ALIGN_ACCESS + } + else { + result = (UInt32)( *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24 ); + } +#endif + + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public UInt64 ReadUInt64(Int64 position) { + int sizeOfType = sizeof(UInt64); + EnsureSafeToRead(position, sizeOfType); + + UInt64 result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + result = *((UInt64*)(pointer)); +#if ALIGN_ACCESS + } + else { + UInt32 lo = (UInt32)( *pointer | *(pointer + 1) << 8 | *(pointer + 2) << 16 | *(pointer + 3) << 24 ); + UInt32 hi = (UInt32)( *(pointer + 4) | *(pointer + 5) << 8 | *(pointer + 6) << 16 | *(pointer + 7) << 24 ); + result = (UInt64)(((UInt64)hi << 32) | lo ); + } +#endif + + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + return result; + } + + // 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. Furthermore, using this with a struct that + // contains reference members will most likely cause the runtime to AV. Note, that despite + // various checks made by the C++ code used by Marshal.PtrToStructure, Marshal.PtrToStructure + // will still overwrite privates and will also crash the runtime when used with structs + // containing reference members. For this reason, I am sticking an UnmanagedCode requirement + // on this method to match Marshal.PtrToStructure. + + // Alos 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 ReadXXX methods for small standard types such as ints, longs, + // bools, etc. + + [System.Security.SecurityCritical] // auto-generated_required + public void Read<T>(Int64 position, out T structure) where T : struct { + if (position < 0) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + Contract.EndContractBlock(); + + if (!_isOpen) { + throw new ObjectDisposedException("UnmanagedMemoryAccessor", Environment.GetResourceString("ObjectDisposed_ViewAccessorClosed")); + } + if (!CanRead) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_Reading")); + } + + UInt32 sizeOfT = Marshal.SizeOfType(typeof(T)); + if (position > _capacity - sizeOfT) { + if (position >= _capacity) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + } + else { + throw new ArgumentException(Environment.GetResourceString("Argument_NotEnoughBytesToRead", typeof(T).FullName), "position"); + } + } + + structure = _buffer.Read<T>((UInt64)(_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. Furthermore, using this with a + // struct that contains reference members will most likely cause the runtime to AV. This + // is consistent with Marshal.PtrToStructure. + + [System.Security.SecurityCritical] // auto-generated_required + public int ReadArray<T>(Int64 position, T[] array, Int32 offset, Int32 count) where T : struct { + if (array == null) { + throw new ArgumentNullException("array", "Buffer cannot be null."); + } + if (offset < 0) { + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (count < 0) { + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (array.Length - offset < count) { + throw new ArgumentException(Environment.GetResourceString("Argument_OffsetAndLengthOutOfBounds")); + } + Contract.EndContractBlock(); + if (!CanRead) { + if (!_isOpen) { + throw new ObjectDisposedException("UnmanagedMemoryAccessor", Environment.GetResourceString("ObjectDisposed_ViewAccessorClosed")); + } + else { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_Reading")); + } + } + if (position < 0) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + + UInt32 sizeOfT = Marshal.AlignedSizeOf<T>(); + + // only check position and ask for fewer Ts if count is too big + if (position >= _capacity) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("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>((UInt64)(_offset + position), array, offset, n); + + return n; + } + + // ************** Write Methods ****************/ + + // The following 13 WriteXXX methods write a value of type XXX into unmanaged memory at 'positon'. + // The bounds of the unmanaged memory are checked against to ensure that there is enough + // space after 'position' to write a value of type XXX. XXX can be a bool, byte, char, decimal, + // double, short, int, long, sbyte, float, ushort, uint, or ulong. + + + public void Write(Int64 position, bool value) { + int sizeOfType = sizeof(bool); + EnsureSafeToWrite(position, sizeOfType); + + byte b = (byte)(value ? 1 : 0); + InternalWrite(position, b); + } + + public void Write(Int64 position, byte value) { + int sizeOfType = sizeof(byte); + EnsureSafeToWrite(position, sizeOfType); + + InternalWrite(position, value); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void Write(Int64 position, char value) { + int sizeOfType = sizeof(char); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((char*)pointer) = value; +#if ALIGN_ACCESS + } + else { + *(pointer) = (byte)value; + *(pointer+1) = (byte)(value >> 8); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public void Write(Int64 position, Int16 value) { + int sizeOfType = sizeof(Int16); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((Int16*)pointer) = value; +#if ALIGN_ACCESS + } + else { + *(pointer) = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public void Write(Int64 position, Int32 value) { + int sizeOfType = sizeof(Int32); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((Int32*)pointer) = value; +#if ALIGN_ACCESS + } + else { + *(pointer) = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void Write(Int64 position, Int64 value) { + int sizeOfType = sizeof(Int64); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((Int64*)pointer) = value; +#if ALIGN_ACCESS + } + else { + *(pointer) = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + *(pointer + 4) = (byte)(value >> 32); + *(pointer + 5) = (byte)(value >> 40); + *(pointer + 6) = (byte)(value >> 48); + *(pointer + 7) = (byte)(value >> 56); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void Write(Int64 position, Decimal value) { + int sizeOfType = sizeof(Decimal); + EnsureSafeToWrite(position, sizeOfType); + + byte[] decimalArray = new byte[16]; + Decimal.GetBytes(value, decimalArray); + + int[] bits = new int[4]; + int flags = ((int)decimalArray[12]) | ((int)decimalArray[13] << 8) | ((int)decimalArray[14] << 16) | ((int)decimalArray[15] << 24); + int lo = ((int)decimalArray[0]) | ((int)decimalArray[1] << 8) | ((int)decimalArray[2] << 16) | ((int)decimalArray[3] << 24); + int mid = ((int)decimalArray[4]) | ((int)decimalArray[5] << 8) | ((int)decimalArray[6] << 16) | ((int)decimalArray[7] << 24); + int hi = ((int)decimalArray[8]) | ((int)decimalArray[9] << 8) | ((int)decimalArray[10] << 16) | ((int)decimalArray[11] << 24); + bits[0] = lo; + bits[1] = mid; + bits[2] = hi; + bits[3] = flags; + + WriteArray<int>(position, bits, 0, bits.Length); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void Write(Int64 position, Single value) { + int sizeOfType = sizeof(Single); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((Single*)pointer) = value; +#if ALIGN_ACCESS + } + else { + UInt32 tmpValue = *(UInt32*)&value; + *(pointer) = (byte)tmpValue; + *(pointer + 1) = (byte)(tmpValue >> 8); + *(pointer + 2) = (byte)(tmpValue >> 16); + *(pointer + 3) = (byte)(tmpValue >> 24); + + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void Write(Int64 position, Double value) { + int sizeOfType = sizeof(Double); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((Double*)pointer) = value; +#if ALIGN_ACCESS + } + else { + UInt64 tmpValue = *(UInt64 *)&value; + *(pointer) = (byte) tmpValue; + *(pointer + 1) = (byte) (tmpValue >> 8); + *(pointer + 2) = (byte) (tmpValue >> 16); + *(pointer + 3) = (byte) (tmpValue >> 24); + *(pointer + 4) = (byte) (tmpValue >> 32); + *(pointer + 5) = (byte) (tmpValue >> 40); + *(pointer + 6) = (byte) (tmpValue >> 48); + *(pointer + 7) = (byte) (tmpValue >> 56); + + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public void Write(Int64 position, SByte value) { + int sizeOfType = sizeof(SByte); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + *((SByte*)pointer) = value; + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public void Write(Int64 position, UInt16 value) { + int sizeOfType = sizeof(UInt16); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((UInt16*)pointer) = value; +#if ALIGN_ACCESS + } + else { + *(pointer) = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + } +#endif + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public void Write(Int64 position, UInt32 value) { + int sizeOfType = sizeof(UInt32); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); + +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((UInt32*)pointer) = value; +#if ALIGN_ACCESS + } + else { + *(pointer) = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + } +#endif + + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + [CLSCompliant(false)] + public void Write(Int64 position, UInt64 value) { + int sizeOfType = sizeof(UInt64); + EnsureSafeToWrite(position, sizeOfType); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + pointer += (_offset + position); +#if ALIGN_ACCESS + // check if pointer is aligned + if (((int)pointer & (sizeOfType - 1)) == 0) { +#endif + *((UInt64*)pointer) = value; +#if ALIGN_ACCESS + } + else { + *(pointer) = (byte)value; + *(pointer + 1) = (byte)(value >> 8); + *(pointer + 2) = (byte)(value >> 16); + *(pointer + 3) = (byte)(value >> 24); + *(pointer + 4) = (byte)(value >> 32); + *(pointer + 5) = (byte)(value >> 40); + *(pointer + 6) = (byte)(value >> 48); + *(pointer + 7) = (byte)(value >> 56); + } +#endif + + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + // 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. + + [System.Security.SecurityCritical] // auto-generated_required + public void Write<T>(Int64 position, ref T structure) where T : struct { + if (position < 0) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + Contract.EndContractBlock(); + + if (!_isOpen) { + throw new ObjectDisposedException("UnmanagedMemoryAccessor", Environment.GetResourceString("ObjectDisposed_ViewAccessorClosed")); + } + if (!CanWrite) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_Writing")); + } + + UInt32 sizeOfT = Marshal.SizeOfType(typeof(T)); + if (position > _capacity - sizeOfT) { + if (position >= _capacity) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + } + else { + throw new ArgumentException(Environment.GetResourceString("Argument_NotEnoughBytesToWrite", typeof(T).FullName), "position"); + } + } + + _buffer.Write<T>((UInt64)(_offset + position), structure); + } + + // Writes 'count' structs of type T from 'array' (starting at 'offset') into unmanaged memory. + + + [System.Security.SecurityCritical] // auto-generated_required + public void WriteArray<T>(Int64 position, T[] array, Int32 offset, Int32 count) where T : struct { + if (array == null) { + throw new ArgumentNullException("array", "Buffer cannot be null."); + } + if (offset < 0) { + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (count < 0) { + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (array.Length - offset < count) { + throw new ArgumentException(Environment.GetResourceString("Argument_OffsetAndLengthOutOfBounds")); + } + if (position < 0) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (position >= Capacity) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + } + Contract.EndContractBlock(); + + if (!_isOpen) { + throw new ObjectDisposedException("UnmanagedMemoryAccessor", Environment.GetResourceString("ObjectDisposed_ViewAccessorClosed")); + } + if (!CanWrite) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_Writing")); + } + + _buffer.WriteArray<T>((UInt64)(_offset + position), array, offset, count); + } + + [System.Security.SecuritySafeCritical] // auto-generated + private byte InternalReadByte(Int64 position) { + Contract.Assert(CanRead, "UMA not readable"); + Contract.Assert(position >= 0, "position less than 0"); + Contract.Assert(position <= _capacity - sizeof(byte), "position is greater than capacity - sizeof(byte)"); + + byte result; + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + result = *((byte*)(pointer + _offset + position)); + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + return result; + } + + [System.Security.SecuritySafeCritical] // auto-generated + private void InternalWrite(Int64 position, byte value) { + Contract.Assert(CanWrite, "UMA not writeable"); + Contract.Assert(position >= 0, "position less than 0"); + Contract.Assert(position <= _capacity - sizeof(byte), "position is greater than capacity - sizeof(byte)"); + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + *((byte*)(pointer + _offset + position)) = value; + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + + private void EnsureSafeToRead(Int64 position, int sizeOfType) { + if (!_isOpen) { + throw new ObjectDisposedException("UnmanagedMemoryAccessor", Environment.GetResourceString("ObjectDisposed_ViewAccessorClosed")); + } + if (!CanRead) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_Reading")); + } + if (position < 0) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + Contract.EndContractBlock(); + if (position > _capacity - sizeOfType) { + if (position >= _capacity) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + } + else { + throw new ArgumentException(Environment.GetResourceString("Argument_NotEnoughBytesToRead"), "position"); + } + } + } + + private void EnsureSafeToWrite(Int64 position, int sizeOfType) { + if (!_isOpen) { + throw new ObjectDisposedException("UnmanagedMemoryAccessor", Environment.GetResourceString("ObjectDisposed_ViewAccessorClosed")); + } + if (!CanWrite) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_Writing")); + } + if (position < 0) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + Contract.EndContractBlock(); + if (position > _capacity - sizeOfType) { + if (position >= _capacity) { + throw new ArgumentOutOfRangeException("position", Environment.GetResourceString("ArgumentOutOfRange_PositionLessThanCapacityRequired")); + } + else { + throw new ArgumentException(Environment.GetResourceString("Argument_NotEnoughBytesToWrite", "Byte"), "position"); + } + } + } + + } +} diff --git a/src/mscorlib/src/System/IO/UnmanagedMemoryStream.cs b/src/mscorlib/src/System/IO/UnmanagedMemoryStream.cs new file mode 100644 index 0000000000..6b506ea5b1 --- /dev/null +++ b/src/mscorlib/src/System/IO/UnmanagedMemoryStream.cs @@ -0,0 +1,712 @@ +// 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 stream over unmanaged memory, mostly +** useful for memory-mapped files. +** +** +===========================================================*/ +using System; +using System.Runtime; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; +using System.Security.Permissions; +using System.Threading; +using System.Diagnostics.Contracts; +using System.Threading.Tasks; + + +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 etc. + * We'll suggest user write a subclass of UnmanagedMemoryStream that uses + * a SafeHandle subclass to hold onto the memory. + * Check for problems when using this in the negative parts of a + * process's address space. We may need to use unsigned longs internally + * and change the overflow detection logic. + * + * -----SECURITY MODEL AND SILVERLIGHT----- + * A few key notes about exposing UMS in silverlight: + * 1. No ctors are exposed to transparent code. This version of UMS only + * supports byte* (not SafeBuffer). Therefore, framework code can create + * a UMS and hand it to transparent code. Transparent code can use most + * operations on a UMS, but not operations that directly expose a + * pointer. + * + * 2. Scope of "unsafe" and non-CLS compliant operations reduced: The + * Whidbey version of this class has CLSCompliant(false) at the class + * level and unsafe modifiers at the method level. These were reduced to + * only where the unsafe operation is performed -- i.e. immediately + * around the pointer manipulation. Note that this brings UMS in line + * with recent changes in pu/clr to support SafeBuffer. + * + * 3. Currently, the only caller that creates a UMS is ResourceManager, + * which creates read-only UMSs, and therefore operations that can + * change the length will throw because write isn't supported. A + * conservative option would be to formalize the concept that _only_ + * read-only UMSs can be creates, and enforce this by making WriteX and + * SetLength SecurityCritical. However, this is a violation of + * security inheritance rules, so we must keep these safe. The + * following notes make this acceptable for future use. + * a. a race condition in WriteX that could have allowed a thread to + * read from unzeroed memory was fixed + * b. memory region cannot be expanded beyond _capacity; in other + * words, a UMS creator is saying a writeable UMS is safe to + * write to anywhere in the memory range up to _capacity, specified + * in the ctor. Even if the caller doesn't specify a capacity, then + * length is used as the capacity. + */ + public class UnmanagedMemoryStream : Stream + { + private const long UnmanagedMemStreamMaxLength = Int64.MaxValue; + + [System.Security.SecurityCritical] // auto-generated + private SafeBuffer _buffer; + [SecurityCritical] + private unsafe byte* _mem; + private long _length; + private long _capacity; + private long _position; + private long _offset; + private FileAccess _access; + internal bool _isOpen; + [NonSerialized] + private Task<Int32> _lastReadTask; // The last successful task returned from ReadAsync + + + // Needed for subclasses that need to map a file, etc. + [System.Security.SecuritySafeCritical] // auto-generated + protected UnmanagedMemoryStream() + { + unsafe { + _mem = null; + } + _isOpen = false; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length) { + Initialize(buffer, offset, length, FileAccess.Read, false); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access) { + Initialize(buffer, offset, length, access, false); + } + + // We must create one of these without doing a security check. This + // class is created while security is trying to start up. Plus, doing + // a Demand from Assembly.GetManifestResourceStream isn't useful. + [System.Security.SecurityCritical] // auto-generated + internal UnmanagedMemoryStream(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck) { + Initialize(buffer, offset, length, access, skipSecurityCheck); + } + + [System.Security.SecuritySafeCritical] // auto-generated + protected void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access) { + Initialize(buffer, offset, length, access, false); + } + + [System.Security.SecurityCritical] // auto-generated + internal void Initialize(SafeBuffer buffer, long offset, long length, FileAccess access, bool skipSecurityCheck) { + if (buffer == null) { + throw new ArgumentNullException("buffer"); + } + if (offset < 0) { + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (length < 0) { + throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + } + if (buffer.ByteLength < (ulong)(offset + length)) { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSafeBufferOffLen")); + } + if (access < FileAccess.Read || access > FileAccess.ReadWrite) { + throw new ArgumentOutOfRangeException("access"); + } + Contract.EndContractBlock(); + + if (_isOpen) { + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CalledTwice")); + } + if (!skipSecurityCheck) { +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); +#pragma warning restore 618 + } + + // check for wraparound + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + buffer.AcquirePointer(ref pointer); + if ( (pointer + offset + length) < pointer) { + throw new ArgumentException(Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround")); + } + } + finally { + if (pointer != null) { + buffer.ReleasePointer(); + } + } + } + + _offset = offset; + _buffer = buffer; + _length = length; + _capacity = length; + _access = access; + _isOpen = true; + } + + [System.Security.SecurityCritical] // auto-generated + [CLSCompliant(false)] + public unsafe UnmanagedMemoryStream(byte* pointer, long length) + { + Initialize(pointer, length, length, FileAccess.Read, false); + } + + [System.Security.SecurityCritical] // auto-generated + [CLSCompliant(false)] + public unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access) + { + Initialize(pointer, length, capacity, access, false); + } + + // We must create one of these without doing a security check. This + // class is created while security is trying to start up. Plus, doing + // a Demand from Assembly.GetManifestResourceStream isn't useful. + [System.Security.SecurityCritical] // auto-generated + internal unsafe UnmanagedMemoryStream(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck) + { + Initialize(pointer, length, capacity, access, skipSecurityCheck); + } + + [System.Security.SecurityCritical] // auto-generated + [CLSCompliant(false)] + protected unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access) + { + Initialize(pointer, length, capacity, access, false); + } + + [System.Security.SecurityCritical] // auto-generated + internal unsafe void Initialize(byte* pointer, long length, long capacity, FileAccess access, bool skipSecurityCheck) + { + if (pointer == null) + throw new ArgumentNullException("pointer"); + if (length < 0 || capacity < 0) + throw new ArgumentOutOfRangeException((length < 0) ? "length" : "capacity", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (length > capacity) + throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_LengthGreaterThanCapacity")); + Contract.EndContractBlock(); + // Check for wraparound. + if (((byte*) ((long)pointer + capacity)) < pointer) + throw new ArgumentOutOfRangeException("capacity", Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamWrapAround")); + if (access < FileAccess.Read || access > FileAccess.ReadWrite) + throw new ArgumentOutOfRangeException("access", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + if (_isOpen) + throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_CalledTwice")); + + if (!skipSecurityCheck) +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Demand(); +#pragma warning restore 618 + + _mem = pointer; + _offset = 0; + _length = length; + _capacity = capacity; + _access = access; + _isOpen = true; + } + + public override bool CanRead { + [Pure] + get { return _isOpen && (_access & FileAccess.Read) != 0; } + } + + public override bool CanSeek { + [Pure] + get { return _isOpen; } + } + + public override bool CanWrite { + [Pure] + get { return _isOpen && (_access & FileAccess.Write) != 0; } + } + + [System.Security.SecuritySafeCritical] // auto-generated + 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); + } + + public override void Flush() { + if (!_isOpen) __Error.StreamIsClosed(); + } + + [HostProtection(ExternalThreading=true)] + [ComVisible(false)] + 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 override long Length { + get { + if (!_isOpen) __Error.StreamIsClosed(); + return Interlocked.Read(ref _length); + } + } + + public long Capacity { + get { + if (!_isOpen) __Error.StreamIsClosed(); + return _capacity; + } + } + + public override long Position { + get { + if (!CanSeek) __Error.StreamIsClosed(); + Contract.EndContractBlock(); + return Interlocked.Read(ref _position); + } + [System.Security.SecuritySafeCritical] // auto-generated + set { + if (value < 0) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (!CanSeek) __Error.StreamIsClosed(); + +#if !BIT64 + unsafe { + // On 32 bit machines, ensure we don't wrap around. + if (value > (long) Int32.MaxValue || _mem + value < _mem) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_StreamLength")); + } +#endif + Interlocked.Exchange(ref _position, value); + } + } + + [CLSCompliant(false)] + public unsafe byte* PositionPointer { + [System.Security.SecurityCritical] // auto-generated_required + get { + if (_buffer != null) { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer")); + } + + // Use a temp to avoid a race + long pos = Interlocked.Read(ref _position); + if (pos > _capacity) + throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_UMSPosition")); + byte * ptr = _mem + pos; + if (!_isOpen) __Error.StreamIsClosed(); + return ptr; + } + [System.Security.SecurityCritical] // auto-generated_required + set { + if (_buffer != null) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer")); + if (!_isOpen) __Error.StreamIsClosed(); + + // Note: subtracting pointers returns an Int64. Working around + // to avoid hitting compiler warning CS0652 on this line. + if (new IntPtr(value - _mem).ToInt64() > UnmanagedMemStreamMaxLength) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamLength")); + if (value < _mem) + throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin")); + + Interlocked.Exchange(ref _position, value - _mem); + } + } + + internal unsafe byte* Pointer { + [System.Security.SecurityCritical] // auto-generated + get { + if (_buffer != null) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer")); + + return _mem; + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override int Read([In, Out] byte[] buffer, int offset, int count) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // Keep this in sync with contract validation in ReadAsync + + if (!_isOpen) __Error.StreamIsClosed(); + if (!CanRead) __Error.ReadNotSupported(); + + // 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 > count) + n = count; + if (n <= 0) + return 0; + + int nInt = (int) n; // Safe because n <= count, which is an Int32 + if (nInt < 0) + nInt = 0; // _position could be beyond EOF + Contract.Assert(pos + nInt >= 0, "_position + n >= 0"); // len is less than 2^63 -1. + + if (_buffer != null) { + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + Buffer.Memcpy(buffer, offset, pointer + pos + _offset, 0, nInt); + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + else { + unsafe { + Buffer.Memcpy(buffer, offset, _mem + pos, 0, nInt); + } + } + Interlocked.Exchange(ref _position, pos + n); + return nInt; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // contract validation copied from Read(...) + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<Int32>(cancellationToken); + + try { + + Int32 n = Read(buffer, offset, count); + Task<Int32> t = _lastReadTask; + return (t != null && t.Result == n) ? t : (_lastReadTask = Task.FromResult<Int32>(n)); + + } catch (Exception ex) { + + Contract.Assert(! (ex is OperationCanceledException)); + return Task.FromException<Int32>(ex); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override int ReadByte() { + if (!_isOpen) __Error.StreamIsClosed(); + if (!CanRead) __Error.ReadNotSupported(); + + 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; + } + + public override long Seek(long offset, SeekOrigin loc) { + if (!_isOpen) __Error.StreamIsClosed(); + if (offset > UnmanagedMemStreamMaxLength) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_UnmanagedMemStreamLength")); + switch(loc) { + case SeekOrigin.Begin: + if (offset < 0) + throw new IOException(Environment.GetResourceString("IO.IO_SeekBeforeBegin")); + Interlocked.Exchange(ref _position, offset); + break; + + case SeekOrigin.Current: + long pos = Interlocked.Read(ref _position); + if (offset + pos < 0) + throw new IOException(Environment.GetResourceString("IO.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(Environment.GetResourceString("IO.IO_SeekBeforeBegin")); + Interlocked.Exchange(ref _position, len + offset); + break; + + default: + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin")); + } + + long finalPos = Interlocked.Read(ref _position); + Contract.Assert(finalPos >= 0, "_position >= 0"); + return finalPos; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void SetLength(long value) { + if (value < 0) + throw new ArgumentOutOfRangeException("length", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_buffer != null) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UmsSafeBuffer")); + if (!_isOpen) __Error.StreamIsClosed(); + if (!CanWrite) __Error.WriteNotSupported(); + + if (value > _capacity) + throw new IOException(Environment.GetResourceString("IO.IO_FixedCapacity")); + + long pos = Interlocked.Read(ref _position); + long len = Interlocked.Read(ref _length); + if (value > len) { + unsafe { + Buffer.ZeroMemory(_mem+len, value-len); + } + } + Interlocked.Exchange(ref _length, value); + if (pos > value) { + Interlocked.Exchange(ref _position, value); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void Write(byte[] buffer, int offset, int count) { + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // Keep contract validation in sync with WriteAsync(..) + + if (!_isOpen) __Error.StreamIsClosed(); + if (!CanWrite) __Error.WriteNotSupported(); + + long pos = Interlocked.Read(ref _position); // Use a local to avoid a race condition + long len = Interlocked.Read(ref _length); + long n = pos + count; + // Check for overflow + if (n < 0) + throw new IOException(Environment.GetResourceString("IO.IO_StreamTooLong")); + + if (n > _capacity) { + throw new NotSupportedException(Environment.GetResourceString("IO.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) { + unsafe { + Buffer.ZeroMemory(_mem+len, pos-len); + } + } + + // set length after zeroing memory to avoid race condition of accessing unzeroed memory + if (n > len) { + Interlocked.Exchange(ref _length, n); + } + } + + if (_buffer != null) { + + long bytesLeft = _capacity - pos; + if (bytesLeft < count) { + throw new ArgumentException(Environment.GetResourceString("Arg_BufferTooSmall")); + } + + unsafe { + byte* pointer = null; + RuntimeHelpers.PrepareConstrainedRegions(); + try { + _buffer.AcquirePointer(ref pointer); + Buffer.Memcpy(pointer + pos + _offset, 0, buffer, offset, count); + } + finally { + if (pointer != null) { + _buffer.ReleasePointer(); + } + } + } + } + else { + unsafe { + Buffer.Memcpy(_mem + pos, 0, buffer, offset, count); + } + } + Interlocked.Exchange(ref _position, n); + return; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) { + + if (buffer==null) + throw new ArgumentNullException("buffer", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); // contract validation copied from Write(..) + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + try { + + Write(buffer, offset, count); + return Task.CompletedTask; + + } catch (Exception ex) { + + Contract.Assert(! (ex is OperationCanceledException)); + return Task.FromException<Int32>(ex); + } + } + + + [System.Security.SecuritySafeCritical] // auto-generated + public override void WriteByte(byte value) { + if (!_isOpen) __Error.StreamIsClosed(); + if (!CanWrite) __Error.WriteNotSupported(); + + 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(Environment.GetResourceString("IO.IO_StreamTooLong")); + + if (n > _capacity) + throw new NotSupportedException(Environment.GetResourceString("IO.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, 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/src/mscorlib/src/System/IO/UnmanagedMemoryStreamWrapper.cs b/src/mscorlib/src/System/IO/UnmanagedMemoryStreamWrapper.cs new file mode 100644 index 0000000000..5727eeddf5 --- /dev/null +++ b/src/mscorlib/src/System/IO/UnmanagedMemoryStreamWrapper.cs @@ -0,0 +1,197 @@ +// 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; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using System.Diagnostics.CodeAnalysis; +using System.Diagnostics.Contracts; +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 UnmanagedMemoryStream _unmanagedStream; + + internal UnmanagedMemoryStreamWrapper(UnmanagedMemoryStream stream) { + _unmanagedStream = stream; + } + + public override bool CanRead { + [Pure] + get { return _unmanagedStream.CanRead; } + } + + public override bool CanSeek { + [Pure] + get { return _unmanagedStream.CanSeek; } + } + + public override bool CanWrite { + [Pure] + get { return _unmanagedStream.CanWrite; } + } + + protected override void Dispose(bool disposing) + { + try { + if (disposing) + _unmanagedStream.Close(); + } + finally { + base.Dispose(disposing); + } + } + + public override void Flush() { + _unmanagedStream.Flush(); + } + + public override byte[] GetBuffer() { + throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_MemStreamBuffer")); + } + + public override bool TryGetBuffer(out ArraySegment<byte> buffer) { + buffer = default(ArraySegment<byte>); + return false; + } + + public override int Capacity { + get { + return (int) _unmanagedStream.Capacity; + } + [SuppressMessage("Microsoft.Contracts", "CC1055")] // Skip extra error checking to avoid *potential* AppCompat problems. + set { + throw new IOException(Environment.GetResourceString("IO.IO_FixedCapacity")); + } + } + + public override long Length { + get { + return _unmanagedStream.Length; + } + } + + public override long Position { + get { + return _unmanagedStream.Position; + } + set { + _unmanagedStream.Position = value; + } + } + + public override int Read([In, Out] byte[] buffer, int offset, int count) { + return _unmanagedStream.Read(buffer, offset, count); + } + + public override int ReadByte() { + return _unmanagedStream.ReadByte(); + } + + public override long Seek(long offset, SeekOrigin loc) { + return _unmanagedStream.Seek(offset, loc); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public unsafe override byte[] ToArray() { + if (!_unmanagedStream._isOpen) __Error.StreamIsClosed(); + if (!_unmanagedStream.CanRead) __Error.ReadNotSupported(); + + byte[] buffer = new byte[_unmanagedStream.Length]; + Buffer.Memcpy(buffer, 0, _unmanagedStream.Pointer, 0, (int)_unmanagedStream.Length); + return buffer; + } + + public override void Write(byte[] buffer, int offset, int count) { + _unmanagedStream.Write(buffer, offset, count); + } + + public override void WriteByte(byte value) { + _unmanagedStream.WriteByte(value); + } + + // Writes this MemoryStream to another stream. + public unsafe override void WriteTo(Stream stream) { + if (stream==null) + throw new ArgumentNullException("stream", Environment.GetResourceString("ArgumentNull_Stream")); + Contract.EndContractBlock(); + + if (!_unmanagedStream._isOpen) __Error.StreamIsClosed(); + if (!CanRead) __Error.ReadNotSupported(); + + byte[] buffer = ToArray(); + + stream.Write(buffer, 0, buffer.Length); + } + + public override void SetLength(Int64 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, Int32 bufferSize, CancellationToken cancellationToken) { + + // The parameter checks must be in sync with the base version: + if (destination == null) + throw new ArgumentNullException("destination"); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + + if (!CanRead && !CanWrite) + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_StreamClosed")); + + if (!destination.CanRead && !destination.CanWrite) + throw new ObjectDisposedException("destination", Environment.GetResourceString("ObjectDisposed_StreamClosed")); + + if (!CanRead) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnreadableStream")); + + if (!destination.CanWrite) + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnwritableStream")); + + Contract.EndContractBlock(); + + return _unmanagedStream.CopyToAsync(destination, bufferSize, cancellationToken); + } + + + public override Task FlushAsync(CancellationToken cancellationToken) { + + return _unmanagedStream.FlushAsync(cancellationToken); + } + + + public override Task<Int32> ReadAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) { + + return _unmanagedStream.ReadAsync(buffer, offset, count, cancellationToken); + } + + + public override Task WriteAsync(Byte[] buffer, Int32 offset, Int32 count, CancellationToken cancellationToken) { + + return _unmanagedStream.WriteAsync(buffer, offset, count, cancellationToken); + } + + } // class UnmanagedMemoryStreamWrapper +} // namespace + + diff --git a/src/mscorlib/src/System/IO/__DebugOutputTextWriter.cs b/src/mscorlib/src/System/IO/__DebugOutputTextWriter.cs new file mode 100644 index 0000000000..621bc41b4b --- /dev/null +++ b/src/mscorlib/src/System/IO/__DebugOutputTextWriter.cs @@ -0,0 +1,76 @@ +// 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 _DEBUG +// This class writes to wherever OutputDebugString writes to. If you don't have +// a Windows app (ie, something hosted in IE), you can use this to redirect Console +// output for some good old-fashioned console spew in MSDEV's debug output window. + +using System; +using System.IO; +using System.Text; +using System.Security; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; +using Microsoft.Win32; +using System.Globalization; + +namespace System.IO { + internal class __DebugOutputTextWriter : TextWriter { + private readonly String _consoleType; + + internal __DebugOutputTextWriter(String consoleType): base(CultureInfo.InvariantCulture) + { + _consoleType = consoleType; + } + + public override Encoding Encoding { +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] +#endif + get { + if (Marshal.SystemDefaultCharSize == 1) + return Encoding.Default; + else + return new UnicodeEncoding(false, false); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void Write(char c) + { + OutputDebugString(c.ToString()); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void Write(String str) + { + OutputDebugString(str); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void Write(char[] array) + { + if (array != null) + OutputDebugString(new String(array)); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void WriteLine(String str) + { + if (str != null) + OutputDebugString(_consoleType + str); + else + OutputDebugString("<null>"); + OutputDebugString(new String(CoreNewLine)); + } + + [System.Security.SecurityCritical] // auto-generated + [DllImport(Win32Native.KERNEL32, CharSet=CharSet.Auto)] + [SuppressUnmanagedCodeSecurityAttribute()] + private static extern void OutputDebugString(String output); + } +} + +#endif // _DEBUG diff --git a/src/mscorlib/src/System/IO/__Error.cs b/src/mscorlib/src/System/IO/__Error.cs new file mode 100644 index 0000000000..a31d9657e8 --- /dev/null +++ b/src/mscorlib/src/System/IO/__Error.cs @@ -0,0 +1,232 @@ +// 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: Centralized error methods for the IO package. +** Mostly useful for translating Win32 HRESULTs into meaningful +** error strings & exceptions. +** +** +===========================================================*/ + +using System; +using System.Runtime.InteropServices; +using Win32Native = Microsoft.Win32.Win32Native; +using System.Text; +using System.Globalization; +using System.Security; +using System.Security.Permissions; +using System.Diagnostics.Contracts; + +namespace System.IO { + [Pure] + internal static class __Error + { + internal static void EndOfFile() { + throw new EndOfStreamException(Environment.GetResourceString("IO.EOF_ReadBeyondEOF")); + } + + internal static void FileNotOpen() { + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_FileClosed")); + } + + internal static void StreamIsClosed() { + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_StreamClosed")); + } + + internal static void MemoryStreamNotExpandable() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_MemStreamNotExpandable")); + } + + internal static void ReaderClosed() { + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_ReaderClosed")); + } + + internal static void ReadNotSupported() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnreadableStream")); + } + + internal static void SeekNotSupported() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnseekableStream")); + } + + internal static void WrongAsyncResult() { + throw new ArgumentException(Environment.GetResourceString("Arg_WrongAsyncResult")); + } + + internal static void EndReadCalledTwice() { + // Should ideally be InvalidOperationExc but we can't maitain parity with Stream and FileStream without some work + throw new ArgumentException(Environment.GetResourceString("InvalidOperation_EndReadCalledMultiple")); + } + + internal static void EndWriteCalledTwice() { + // Should ideally be InvalidOperationExc but we can't maintain parity with Stream and FileStream without some work + throw new ArgumentException(Environment.GetResourceString("InvalidOperation_EndWriteCalledMultiple")); + } + + // Given a possible fully qualified path, ensure that we have path + // discovery permission to that path. If we do not, return just the + // file name. If we know it is a directory, then don't return the + // directory name. + [System.Security.SecurityCritical] // auto-generated + internal static String GetDisplayablePath(String path, bool isInvalidPath) + { + + if (String.IsNullOrEmpty(path)) + return String.Empty; + + // Is it a fully qualified path? + bool isFullyQualified = false; + if (path.Length < 2) + return path; + if (Path.IsDirectorySeparator(path[0]) && Path.IsDirectorySeparator(path[1])) + isFullyQualified = true; + else if (path[1] == Path.VolumeSeparatorChar) { + isFullyQualified = true; + } + + if (!isFullyQualified && !isInvalidPath) + return path; + + bool safeToReturn = false; + try { + if (!isInvalidPath) { +#if !FEATURE_CORECLR + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { path }, false, false).Demand(); +#endif + safeToReturn = true; + } + } + catch (SecurityException) { + } + catch (ArgumentException) { + // ? and * characters cause ArgumentException to be thrown from HasIllegalCharacters + // inside FileIOPermission.AddPathList + } + catch (NotSupportedException) { + // paths like "!Bogus\\dir:with/junk_.in it" can cause NotSupportedException to be thrown + // from Security.Util.StringExpressionSet.CanonicalizePath when ':' is found in the path + // beyond string index position 1. + } + + if (!safeToReturn) { + if (Path.IsDirectorySeparator(path[path.Length - 1])) + path = Environment.GetResourceString("IO.IO_NoPermissionToDirectoryName"); + else + path = Path.GetFileName(path); + } + + return path; + } + + [System.Security.SecuritySafeCritical] // auto-generated + internal static void WinIOError() { + int errorCode = Marshal.GetLastWin32Error(); + WinIOError(errorCode, String.Empty); + } + + // After calling GetLastWin32Error(), it clears the last error field, + // so you must save the HResult and pass it to this method. This method + // will determine the appropriate exception to throw dependent on your + // error, and depending on the error, insert a string into the message + // gotten from the ResourceManager. + [System.Security.SecurityCritical] // auto-generated + internal static void WinIOError(int errorCode, String maybeFullPath) { + // This doesn't have to be perfect, but is a perf optimization. + bool isInvalidPath = errorCode == Win32Native.ERROR_INVALID_NAME || errorCode == Win32Native.ERROR_BAD_PATHNAME; + String str = GetDisplayablePath(maybeFullPath, isInvalidPath); + + switch (errorCode) { + case Win32Native.ERROR_FILE_NOT_FOUND: + if (str.Length == 0) + throw new FileNotFoundException(Environment.GetResourceString("IO.FileNotFound")); + else + throw new FileNotFoundException(Environment.GetResourceString("IO.FileNotFound_FileName", str), str); + + case Win32Native.ERROR_PATH_NOT_FOUND: + if (str.Length == 0) + throw new DirectoryNotFoundException(Environment.GetResourceString("IO.PathNotFound_NoPathName")); + else + throw new DirectoryNotFoundException(Environment.GetResourceString("IO.PathNotFound_Path", str)); + + case Win32Native.ERROR_ACCESS_DENIED: + if (str.Length == 0) + throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_IODenied_NoPathName")); + else + throw new UnauthorizedAccessException(Environment.GetResourceString("UnauthorizedAccess_IODenied_Path", str)); + + case Win32Native.ERROR_ALREADY_EXISTS: + if (str.Length == 0) + goto default; + throw new IOException(Environment.GetResourceString("IO.IO_AlreadyExists_Name", str), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath); + + case Win32Native.ERROR_FILENAME_EXCED_RANGE: + throw new PathTooLongException(Environment.GetResourceString("IO.PathTooLong")); + + case Win32Native.ERROR_INVALID_DRIVE: + throw new DriveNotFoundException(Environment.GetResourceString("IO.DriveNotFound_Drive", str)); + + case Win32Native.ERROR_INVALID_PARAMETER: + throw new IOException(Win32Native.GetMessage(errorCode), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath); + + case Win32Native.ERROR_SHARING_VIOLATION: + if (str.Length == 0) + throw new IOException(Environment.GetResourceString("IO.IO_SharingViolation_NoFileName"), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath); + else + throw new IOException(Environment.GetResourceString("IO.IO_SharingViolation_File", str), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath); + + case Win32Native.ERROR_FILE_EXISTS: + if (str.Length == 0) + goto default; + throw new IOException(Environment.GetResourceString("IO.IO_FileExists_Name", str), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath); + + case Win32Native.ERROR_OPERATION_ABORTED: + throw new OperationCanceledException(); + + default: + throw new IOException(Win32Native.GetMessage(errorCode), Win32Native.MakeHRFromErrorCode(errorCode), maybeFullPath); + } + } + + // An alternative to WinIOError with friendlier messages for drives + [System.Security.SecuritySafeCritical] // auto-generated + internal static void WinIODriveError(String driveName) { + int errorCode = Marshal.GetLastWin32Error(); + WinIODriveError(driveName, errorCode); + } + + [System.Security.SecurityCritical] // auto-generated + internal static void WinIODriveError(String driveName, int errorCode) + { + switch (errorCode) { + case Win32Native.ERROR_PATH_NOT_FOUND: + case Win32Native.ERROR_INVALID_DRIVE: + throw new DriveNotFoundException(Environment.GetResourceString("IO.DriveNotFound_Drive", driveName)); + + default: + WinIOError(errorCode, driveName); + break; + } + } + + internal static void WriteNotSupported() { + throw new NotSupportedException(Environment.GetResourceString("NotSupported_UnwritableStream")); + } + + internal static void WriterClosed() { + throw new ObjectDisposedException(null, Environment.GetResourceString("ObjectDisposed_WriterClosed")); + } + + // From WinError.h + internal const int ERROR_FILE_NOT_FOUND = Win32Native.ERROR_FILE_NOT_FOUND; + internal const int ERROR_PATH_NOT_FOUND = Win32Native.ERROR_PATH_NOT_FOUND; + internal const int ERROR_ACCESS_DENIED = Win32Native.ERROR_ACCESS_DENIED; + internal const int ERROR_INVALID_PARAMETER = Win32Native.ERROR_INVALID_PARAMETER; + } +} diff --git a/src/mscorlib/src/System/IO/__HResults.cs b/src/mscorlib/src/System/IO/__HResults.cs new file mode 100644 index 0000000000..e19f28f833 --- /dev/null +++ b/src/mscorlib/src/System/IO/__HResults.cs @@ -0,0 +1,28 @@ +// 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: Define HResult constants. Every exception has one of these. +// +// +//===========================================================================*/ +namespace System.IO { + using System; + // Only static data no need to serialize + internal static class __HResults + { + // These use an error code from WinError.h + public const int COR_E_ENDOFSTREAM = unchecked((int)0x80070026); // OS defined + public const int COR_E_FILELOAD = unchecked((int)0x80131621); + public const int COR_E_FILENOTFOUND = unchecked((int)0x80070002); + public const int COR_E_DIRECTORYNOTFOUND = unchecked((int)0x80070003); + public const int COR_E_PATHTOOLONG = unchecked((int)0x800700CE); + + public const int COR_E_IO = unchecked((int)0x80131620); + } +} |