summaryrefslogtreecommitdiff
path: root/src/mscorlib/src/System/IO
diff options
context:
space:
mode:
authorJiyoung Yun <jy910.yun@samsung.com>2016-11-23 10:09:09 (GMT)
committerJiyoung Yun <jy910.yun@samsung.com>2016-11-23 10:09:09 (GMT)
commit4b4aad7217d3292650e77eec2cf4c198ea9c3b4b (patch)
tree98110734c91668dfdbb126fcc0e15ddbd93738ca /src/mscorlib/src/System/IO
parentfa45f57ed55137c75ac870356a1b8f76c84b229c (diff)
downloadcoreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.zip
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.gz
coreclr-4b4aad7217d3292650e77eec2cf4c198ea9c3b4b.tar.bz2
Imported Upstream version 1.1.0upstream/1.1.0
Diffstat (limited to 'src/mscorlib/src/System/IO')
-rw-r--r--src/mscorlib/src/System/IO/BinaryReader.cs601
-rw-r--r--src/mscorlib/src/System/IO/BinaryWriter.cs427
-rw-r--r--src/mscorlib/src/System/IO/BufferedStream.cs1320
-rw-r--r--src/mscorlib/src/System/IO/Directory.cs1389
-rw-r--r--src/mscorlib/src/System/IO/DirectoryInfo.cs672
-rw-r--r--src/mscorlib/src/System/IO/DirectoryNotFoundException.cs46
-rw-r--r--src/mscorlib/src/System/IO/DriveInfo.cs281
-rw-r--r--src/mscorlib/src/System/IO/DriveNotFoundException.cs40
-rw-r--r--src/mscorlib/src/System/IO/EndOfStreamException.cs43
-rw-r--r--src/mscorlib/src/System/IO/File.cs1255
-rw-r--r--src/mscorlib/src/System/IO/FileAccess.cs42
-rw-r--r--src/mscorlib/src/System/IO/FileAttributes.cs51
-rw-r--r--src/mscorlib/src/System/IO/FileInfo.cs431
-rw-r--r--src/mscorlib/src/System/IO/FileLoadException.cs191
-rw-r--r--src/mscorlib/src/System/IO/FileMode.cs54
-rw-r--r--src/mscorlib/src/System/IO/FileNotFoundException.cs170
-rw-r--r--src/mscorlib/src/System/IO/FileOptions.cs47
-rw-r--r--src/mscorlib/src/System/IO/FileSecurityState.cs133
-rw-r--r--src/mscorlib/src/System/IO/FileSecurityStateAccess.cs32
-rw-r--r--src/mscorlib/src/System/IO/FileShare.cs60
-rw-r--r--src/mscorlib/src/System/IO/FileStream.cs2695
-rw-r--r--src/mscorlib/src/System/IO/FileSystemEnumerable.cs852
-rw-r--r--src/mscorlib/src/System/IO/FileSystemInfo.cs361
-rw-r--r--src/mscorlib/src/System/IO/IOException.cs68
-rw-r--r--src/mscorlib/src/System/IO/LongPathHelper.cs521
-rw-r--r--src/mscorlib/src/System/IO/MemoryStream.cs646
-rw-r--r--src/mscorlib/src/System/IO/Path.cs1435
-rw-r--r--src/mscorlib/src/System/IO/PathHelper.cs448
-rw-r--r--src/mscorlib/src/System/IO/PathInternal.cs806
-rw-r--r--src/mscorlib/src/System/IO/PathTooLongException.cs44
-rw-r--r--src/mscorlib/src/System/IO/PinnedBufferMemoryStream.cs73
-rw-r--r--src/mscorlib/src/System/IO/ReadLinesIterator.cs102
-rw-r--r--src/mscorlib/src/System/IO/SearchOption.cs36
-rw-r--r--src/mscorlib/src/System/IO/SeekOrigin.cs32
-rw-r--r--src/mscorlib/src/System/IO/Stream.cs1304
-rw-r--r--src/mscorlib/src/System/IO/StreamReader.cs1293
-rw-r--r--src/mscorlib/src/System/IO/StreamWriter.cs866
-rw-r--r--src/mscorlib/src/System/IO/StringReader.cs187
-rw-r--r--src/mscorlib/src/System/IO/StringWriter.cs196
-rw-r--r--src/mscorlib/src/System/IO/TextReader.cs413
-rw-r--r--src/mscorlib/src/System/IO/TextWriter.cs915
-rw-r--r--src/mscorlib/src/System/IO/UnmanagedMemoryAccessor.cs1176
-rw-r--r--src/mscorlib/src/System/IO/UnmanagedMemoryStream.cs712
-rw-r--r--src/mscorlib/src/System/IO/UnmanagedMemoryStreamWrapper.cs197
-rw-r--r--src/mscorlib/src/System/IO/__DebugOutputTextWriter.cs76
-rw-r--r--src/mscorlib/src/System/IO/__Error.cs232
-rw-r--r--src/mscorlib/src/System/IO/__HResults.cs28
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 0000000..8accf0b
--- /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 0000000..c775cbc
--- /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 0000000..0c73b5c
--- /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 0000000..be74538
--- /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 0000000..f7b0709
--- /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 0000000..09d7e7d
--- /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 0000000..be75e89
--- /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 0000000..04155bc
--- /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 0000000..60f5109
--- /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 0000000..cfcb469
--- /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 0000000..f708a28
--- /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 0000000..19d5f22
--- /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 0000000..3ab1a51
--- /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 0000000..fabe261
--- /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 0000000..9b9c202
--- /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 0000000..933e4fd
--- /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 0000000..ffd6dda
--- /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 0000000..249848a
--- /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 0000000..b6378c6
--- /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 0000000..ad76d5d
--- /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 0000000..deef30c
--- /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 0000000..c2e603c
--- /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 0000000..7a17a41
--- /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 0000000..b3db033
--- /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 0000000..9746fdc
--- /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 0000000..edb583b
--- /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 0000000..4f79936
--- /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 0000000..8e39b3c
--- /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 0000000..3970e22
--- /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 (", &lt;, &gt;, |
+ /// NUL, or any ASCII char whose integer representation is in the range of 1 through 31).
+ /// Does not check for wild card characters ? and *.
+ ///
+ /// 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)