diff options
Diffstat (limited to 'src/mscorlib/src/System/IO/FileStream.cs')
-rw-r--r-- | src/mscorlib/src/System/IO/FileStream.cs | 2695 |
1 files changed, 2695 insertions, 0 deletions
diff --git a/src/mscorlib/src/System/IO/FileStream.cs b/src/mscorlib/src/System/IO/FileStream.cs new file mode 100644 index 0000000000..deef30c480 --- /dev/null +++ b/src/mscorlib/src/System/IO/FileStream.cs @@ -0,0 +1,2695 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** +** +** Purpose: Exposes a Stream around a file, with full +** synchronous and asychronous support, and buffering. +** +** +===========================================================*/ +using System; +using Microsoft.Win32; +using Microsoft.Win32.SafeHandles; +using System.Security; +#if FEATURE_MACL +using System.Security.AccessControl; +#endif +using System.Security.Permissions; +using System.Threading; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +#if FEATURE_REMOTING +using System.Runtime.Remoting.Messaging; +#endif +using System.Runtime.CompilerServices; +using System.Globalization; +using System.Runtime.Versioning; +using System.Diagnostics.Contracts; +using System.Diagnostics.Tracing; + +/* + * FileStream supports different modes of accessing the disk - async mode + * and sync mode. They are two completely different codepaths in the + * sync & async methods (ie, Read/Write vs. BeginRead/BeginWrite). File + * handles in NT can be opened in only sync or overlapped (async) mode, + * and we have to deal with this pain. Stream has implementations of + * the sync methods in terms of the async ones, so we'll + * call through to our base class to get those methods when necessary. + * + * Also buffering is added into FileStream as well. Folded in the + * code from BufferedStream, so all the comments about it being mostly + * aggressive (and the possible perf improvement) apply to FileStream as + * well. Also added some buffering to the async code paths. + * + * Class Invariants: + * The class has one buffer, shared for reading & writing. It can only be + * used for one or the other at any point in time - not both. The following + * should be true: + * 0 <= _readPos <= _readLen < _bufferSize + * 0 <= _writePos < _bufferSize + * _readPos == _readLen && _readPos > 0 implies the read buffer is valid, + * but we're at the end of the buffer. + * _readPos == _readLen == 0 means the read buffer contains garbage. + * Either _writePos can be greater than 0, or _readLen & _readPos can be + * greater than zero, but neither can be greater than zero at the same time. + * + */ + +namespace System.IO { + + // This is an internal object implementing IAsyncResult with fields + // for all of the relevant data necessary to complete the IO operation. + // This is used by AsyncFSCallback and all of the async methods. + // We should probably make this a nested type of FileStream. But + // I don't know how to define a nested class in mscorlib.h + + unsafe internal sealed class FileStreamAsyncResult : IAsyncResult + { + // README: + // If you modify the order of these fields, make sure to update + // the native VM definition of this class as well!!! + // User code callback + private AsyncCallback _userCallback; + private Object _userStateObject; + private ManualResetEvent _waitHandle; + [System.Security.SecurityCritical] + private SafeFileHandle _handle; // For cancellation support. + + [SecurityCritical] + private NativeOverlapped* _overlapped; + internal NativeOverlapped* OverLapped { [SecurityCritical]get { return _overlapped; } } + internal bool IsAsync { [SecuritySafeCritical]get { return _overlapped != null; } } + + + internal int _EndXxxCalled; // Whether we've called EndXxx already. + private int _numBytes; // number of bytes read OR written + internal int NumBytes { get { return _numBytes; } } + + private int _errorCode; + internal int ErrorCode { get { return _errorCode; } } + + private int _numBufferedBytes; + internal int NumBufferedBytes { get { return _numBufferedBytes; } } + + internal int NumBytesRead { get { return _numBytes + _numBufferedBytes; } } + + private bool _isWrite; // Whether this is a read or a write + internal bool IsWrite { get { return _isWrite; } } + + private bool _isComplete; // Value for IsCompleted property + private bool _completedSynchronously; // Which thread called callback + + // The NativeOverlapped struct keeps a GCHandle to this IAsyncResult object. + // So if the user doesn't call EndRead/EndWrite, a finalizer won't help because + // it'll never get called. + + // Overlapped class will take care of the async IO operations in progress + // when an appdomain unload occurs. + + [System.Security.SecurityCritical] // auto-generated + private unsafe static IOCompletionCallback s_IOCallback; + + [SecuritySafeCritical] + internal FileStreamAsyncResult( + int numBufferedBytes, + byte[] bytes, + SafeFileHandle handle, + AsyncCallback userCallback, + Object userStateObject, + bool isWrite) + { + _userCallback = userCallback; + _userStateObject = userStateObject; + _isWrite = isWrite; + _numBufferedBytes = numBufferedBytes; + _handle = handle; + + // For Synchronous IO, I could go with either a callback and using + // the managed Monitor class, or I could create a handle and wait on it. + ManualResetEvent waitHandle = new ManualResetEvent(false); + _waitHandle = waitHandle; + + // Create a managed overlapped class + // We will set the file offsets later + Overlapped overlapped = new Overlapped(0, 0, IntPtr.Zero, this); + + // Pack the Overlapped class, and store it in the async result + if (userCallback != null) + { + var ioCallback = s_IOCallback; // cached static delegate; delay initialized due to it being SecurityCritical + if (ioCallback == null) s_IOCallback = ioCallback = new IOCompletionCallback(AsyncFSCallback); + _overlapped = overlapped.Pack(ioCallback, bytes); + } + else + { + _overlapped = overlapped.UnsafePack(null, bytes); + } + + Contract.Assert(_overlapped != null, "Did Overlapped.Pack or Overlapped.UnsafePack just return a null?"); + } + + internal static FileStreamAsyncResult CreateBufferedReadResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite) + { + FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytes, userCallback, userStateObject, isWrite); + asyncResult.CallUserCallback(); + return asyncResult; + } + + // This creates a synchronous Async Result. We should consider making this a separate class and maybe merge it with + // System.IO.Stream.SynchronousAsyncResult + private FileStreamAsyncResult(int numBufferedBytes, AsyncCallback userCallback, Object userStateObject, bool isWrite) + { + _userCallback = userCallback; + _userStateObject = userStateObject; + _isWrite = isWrite; + _numBufferedBytes = numBufferedBytes; + } + + public Object AsyncState + { + get { return _userStateObject; } + } + + public bool IsCompleted + { + get { return _isComplete; } + } + + public WaitHandle AsyncWaitHandle + { + [System.Security.SecuritySafeCritical] // auto-generated + get { + // Consider uncommenting this someday soon - the EventHandle + // in the Overlapped struct is really useless half of the + // time today since the OS doesn't signal it. If users call + // EndXxx after the OS call happened to complete, there's no + // reason to create a synchronization primitive here. Fixing + // this will save us some perf, assuming we can correctly + // initialize the ManualResetEvent. + if (_waitHandle == null) { + ManualResetEvent mre = new ManualResetEvent(false); + if (_overlapped != null && _overlapped->EventHandle != IntPtr.Zero) { + mre.SafeWaitHandle = new SafeWaitHandle(_overlapped->EventHandle, true); + } + + // make sure only one thread sets _waitHandle + if (Interlocked.CompareExchange<ManualResetEvent>(ref _waitHandle, mre, null) == null) { + if (_isComplete) + _waitHandle.Set(); + } + else { + // There's a slight but acceptable race condition if we weren't + // the thread that set _waitHandle and this code path + // returns before the code in the if statement + // executes (on the other thread). However, the + // caller is waiting for the wait handle to be set, + // which will still happen. + mre.Close(); + } + } + return _waitHandle; + } + } + + // Returns true iff the user callback was called by the thread that + // called BeginRead or BeginWrite. If we use an async delegate or + // threadpool thread internally, this will be false. This is used + // by code to determine whether a successive call to BeginRead needs + // to be done on their main thread or in their callback to avoid a + // stack overflow on many reads or writes. + public bool CompletedSynchronously + { + get { return _completedSynchronously; } + } + + private void CallUserCallbackWorker() + { + _isComplete = true; + + // ensure _isComplete is set before reading _waitHandle + Thread.MemoryBarrier(); + if (_waitHandle != null) + _waitHandle.Set(); + + _userCallback(this); + } + + internal void CallUserCallback() + { + // Convenience method for me, since I have to do this in a number + // of places in the buffering code for fake IAsyncResults. + // AsyncFSCallback intentionally does not use this method. + + if (_userCallback != null) { + // Call user's callback on a threadpool thread. + // Set completedSynchronously to false, since it's on another + // thread, not the main thread. + _completedSynchronously = false; + ThreadPool.QueueUserWorkItem(state => ((FileStreamAsyncResult)state).CallUserCallbackWorker(), this); + } + else { + _isComplete = true; + + // ensure _isComplete is set before reading _waitHandle + Thread.MemoryBarrier(); + if (_waitHandle != null) + _waitHandle.Set(); + } + } + + [SecurityCritical] + internal void ReleaseNativeResource() + { + // Free memory & GC handles. + if (this._overlapped != null) + Overlapped.Free(_overlapped); + } + + internal void Wait() + { + if (_waitHandle != null) + { + // We must block to ensure that AsyncFSCallback has completed, + // and we should close the WaitHandle in here. AsyncFSCallback + // and the hand-ported imitation version in COMThreadPool.cpp + // are the only places that set this event. + try + { + _waitHandle.WaitOne(); + Contract.Assert(_isComplete == true, "FileStreamAsyncResult::Wait - AsyncFSCallback didn't set _isComplete to true!"); + } + finally + { + _waitHandle.Close(); + } + } + } + + // When doing IO asynchronously (ie, _isAsync==true), this callback is + // called by a free thread in the threadpool when the IO operation + // completes. + [System.Security.SecurityCritical] // auto-generated + unsafe private static void AsyncFSCallback(uint errorCode, uint numBytes, NativeOverlapped* pOverlapped) + { + BCLDebug.Log(String.Format("AsyncFSCallback called. errorCode: " + errorCode + " numBytes: " + numBytes)); + + // Unpack overlapped + Overlapped overlapped = Overlapped.Unpack(pOverlapped); + // Free the overlapped struct in EndRead/EndWrite. + + // Extract async result from overlapped + FileStreamAsyncResult asyncResult = + (FileStreamAsyncResult)overlapped.AsyncResult; + asyncResult._numBytes = (int)numBytes; + + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferReceive((long)(asyncResult.OverLapped), 2, string.Empty); + + // Handle reading from & writing to closed pipes. While I'm not sure + // this is entirely necessary anymore, maybe it's possible for + // an async read on a pipe to be issued and then the pipe is closed, + // returning this error. This may very well be necessary. + if (errorCode == FileStream.ERROR_BROKEN_PIPE || errorCode == FileStream.ERROR_NO_DATA) + errorCode = 0; + + asyncResult._errorCode = (int)errorCode; + + // Call the user-provided callback. It can and often should + // call EndRead or EndWrite. There's no reason to use an async + // delegate here - we're already on a threadpool thread. + // IAsyncResult's completedSynchronously property must return + // false here, saying the user callback was called on another thread. + asyncResult._completedSynchronously = false; + asyncResult._isComplete = true; + + // ensure _isComplete is set before reading _waitHandle + Thread.MemoryBarrier(); + + // The OS does not signal this event. We must do it ourselves. + ManualResetEvent wh = asyncResult._waitHandle; + if (wh != null) + { + Contract.Assert(!wh.SafeWaitHandle.IsClosed, "ManualResetEvent already closed!"); + bool r = wh.Set(); + Contract.Assert(r, "ManualResetEvent::Set failed!"); + if (!r) __Error.WinIOError(); + } + + AsyncCallback userCallback = asyncResult._userCallback; + if (userCallback != null) + userCallback(asyncResult); + } + + [SecuritySafeCritical] + [HostProtection(ExternalThreading = true)] + internal void Cancel() + { + Contract.Assert(_handle != null, "_handle should not be null."); + Contract.Assert(_overlapped != null, "Cancel should only be called on true asynchronous FileStreamAsyncResult, i.e. _overlapped is not null"); + + if (IsCompleted) + return; + + if (_handle.IsInvalid) + return; + + bool r = Win32Native.CancelIoEx(_handle, _overlapped); + if (!r) + { + int errorCode = Marshal.GetLastWin32Error(); + + // ERROR_NOT_FOUND is returned if CancelIoEx cannot find the request to cancel. + // This probably means that the IO operation has completed. + if (errorCode != Win32Native.ERROR_NOT_FOUND) + __Error.WinIOError(errorCode, String.Empty); + } + } + } + + [ComVisible(true)] + public class FileStream : Stream + { + internal const int DefaultBufferSize = 4096; + + private byte[] _buffer; // Shared read/write buffer. Alloc on first use. + private String _fileName; // Fully qualified file name. + private bool _isAsync; // Whether we opened the handle for overlapped IO + private bool _canRead; + private bool _canWrite; + private bool _canSeek; + private bool _exposedHandle; // Could other code be using this handle? + private bool _isPipe; // Whether to disable async buffering code. + private int _readPos; // Read pointer within shared buffer. + private int _readLen; // Number of bytes read in buffer from file. + private int _writePos; // Write pointer within shared buffer. + private int _bufferSize; // Length of internal buffer, if it's allocated. + [System.Security.SecurityCritical] // auto-generated + private SafeFileHandle _handle; + private long _pos; // Cache current location in the file. + private long _appendStart;// When appending, prevent overwriting file. + private static AsyncCallback s_endReadTask; + private static AsyncCallback s_endWriteTask; + private static Action<object> s_cancelReadHandler; + private static Action<object> s_cancelWriteHandler; + + //This exists only to support IsolatedStorageFileStream. + //Any changes to FileStream must include the corresponding changes in IsolatedStorage. + internal FileStream() { + } +#if FEATURE_CORECLR + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode) + : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { + } + + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode, FileAccess access) + : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { + } + + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode, FileAccess access, FileShare share) + : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) { + } + + [System.Security.SecuritySafeCritical] + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize) + : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false, false, true) + { + } + +#else // FEATURE_CORECLR + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode) + : this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access) + : this(path, mode, access, FileShare.Read, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access, FileShare share) + : this(path, mode, access, share, DefaultBufferSize, FileOptions.None, Path.GetFileName(path), false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize) + : this(path, mode, access, share, bufferSize, FileOptions.None, Path.GetFileName(path), false) + { + } +#endif // FEATURE_CORECLR + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + : this(path, mode, access, share, bufferSize, options, Path.GetFileName(path), false) + { + } + + #if FEATURE_CORECLR + [System.Security.SecurityCritical] // auto-generated + #else + [System.Security.SecuritySafeCritical] + #endif + public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) + : this(path, mode, access, share, bufferSize, (useAsync ? FileOptions.Asynchronous : FileOptions.None), Path.GetFileName(path), false) + { + } + +#if FEATURE_MACL + // This constructor is done differently to avoid loading a few more + // classes, and more importantly, to build correctly on Rotor. + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options, FileSecurity fileSecurity) + { + Object pinningHandle; + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share, fileSecurity, out pinningHandle); + try { + Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false); + } + finally { + if (pinningHandle != null) { + GCHandle pinHandle = (GCHandle) pinningHandle; + pinHandle.Free(); + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(String path, FileMode mode, FileSystemRights rights, FileShare share, int bufferSize, FileOptions options) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, (FileAccess)0, (int)rights, true, share, bufferSize, options, secAttrs, Path.GetFileName(path), false, false, false); + } +#endif + + [System.Security.SecurityCritical] // auto-generated + internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, false, false); + } + + [System.Security.SecurityCritical] + internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, false); + } + + [System.Security.SecurityCritical] + internal FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + Init(path, mode, access, 0, false, share, bufferSize, options, secAttrs, msgPath, bFromProxy, useLongPath, checkHost); + } + + // AccessControl namespace is not defined in Rotor + [System.Security.SecuritySafeCritical] + private void Init(String path, FileMode mode, FileAccess access, int rights, bool useRights, FileShare share, int bufferSize, FileOptions options, Win32Native.SECURITY_ATTRIBUTES secAttrs, String msgPath, bool bFromProxy, bool useLongPath, bool checkHost) + { + if (path == null) + throw new ArgumentNullException("path", Environment.GetResourceString("ArgumentNull_Path")); + if (path.Length == 0) + throw new ArgumentException(Environment.GetResourceString("Argument_EmptyPath")); + Contract.EndContractBlock(); + +#if FEATURE_MACL + FileSystemRights fileSystemRights = (FileSystemRights)rights; +#endif + // msgPath must be safe to hand back to untrusted code. + + _fileName = msgPath; // To handle odd cases of finalizing partially constructed objects. + _exposedHandle = false; + + // don't include inheritable in our bounds check for share + FileShare tempshare = share & ~FileShare.Inheritable; + String badArg = null; + + if (mode < FileMode.CreateNew || mode > FileMode.Append) + badArg = "mode"; + else if (!useRights && (access < FileAccess.Read || access > FileAccess.ReadWrite)) + badArg = "access"; +#if FEATURE_MACL + else if (useRights && (fileSystemRights < FileSystemRights.ReadData || fileSystemRights > FileSystemRights.FullControl)) + badArg = "rights"; +#endif + else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete)) + badArg = "share"; + + if (badArg != null) + throw new ArgumentOutOfRangeException(badArg, Environment.GetResourceString("ArgumentOutOfRange_Enum")); + + // NOTE: any change to FileOptions enum needs to be matched here in the error validation + if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0) + throw new ArgumentOutOfRangeException("options", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + + // Write access validation +#if FEATURE_MACL + if ((!useRights && (access & FileAccess.Write) == 0) + || (useRights && (fileSystemRights & FileSystemRights.Write) == 0)) +#else + if (!useRights && (access & FileAccess.Write) == 0) +#endif //FEATURE_MACL + { + if (mode==FileMode.Truncate || mode==FileMode.CreateNew || mode==FileMode.Create || mode==FileMode.Append) { + // No write access + if (!useRights) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&AccessCombo", mode, access)); +#if FEATURE_MACL + else + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileMode&RightsCombo", mode, fileSystemRights)); +#endif //FEATURE_MACL + } + } + +#if FEATURE_MACL + // FileMode.Truncate only works with GENERIC_WRITE (FileAccess.Write), source:MSDN + // For backcomp use FileAccess.Write when FileSystemRights.Write is specified + if (useRights && (mode == FileMode.Truncate)) { + if (fileSystemRights == FileSystemRights.Write) { + useRights = false; + access = FileAccess.Write; + } + else { + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidFileModeTruncate&RightsCombo", mode, fileSystemRights)); + } + } +#endif + + int fAccess; + if (!useRights) { + fAccess = access == FileAccess.Read? GENERIC_READ: + access == FileAccess.Write? GENERIC_WRITE: + GENERIC_READ | GENERIC_WRITE; + } + else { + fAccess = rights; + } + + // Get absolute path - Security needs this to prevent something + // like trying to create a file in c:\tmp with the name + // "..\WinNT\System32\ntoskrnl.exe". Store it for user convenience. + int maxPath = useLongPath ? Path.MaxLongPath : Path.MaxPath; + String filePath = Path.NormalizePath(path, true, maxPath); + + _fileName = filePath; + + // Prevent access to your disk drives as raw block devices. + if (filePath.StartsWith("\\\\.\\", StringComparison.Ordinal)) + throw new ArgumentException(Environment.GetResourceString("Arg_DevicesNotSupported")); + + // In 4.0, we always construct a FileIOPermission object below. + // If filePath contained a ':', we would throw a NotSupportedException in + // System.Security.Util.StringExpressionSet.CanonicalizePath. + // If filePath contained other illegal characters, we would throw an ArgumentException in + // FileIOPermission.CheckIllegalCharacters. + // In 4.5 we on longer construct the FileIOPermission object in full trust. + // To preserve the 4.0 behavior we do an explicit check for ':' here and also call Path.CheckInvalidPathChars. + // Note that we need to call CheckInvalidPathChars before checking for ':' because that is what FileIOPermission does. + + Path.CheckInvalidPathChars(filePath, true); + +#if !PLATFORM_UNIX + if (filePath.IndexOf( ':', 2 ) != -1) + throw new NotSupportedException( Environment.GetResourceString( "Argument_PathFormatNotSupported" ) ); +#endif // !PLATFORM_UNIX + + bool read = false; + +#if FEATURE_MACL + if ((!useRights && (access & FileAccess.Read) != 0) || (useRights && (fileSystemRights & FileSystemRights.ReadAndExecute) != 0)) +#else + if (!useRights && (access & FileAccess.Read) != 0) +#endif //FEATURE_MACL + { + if (mode == FileMode.Append) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidAppendMode")); + else + read = true; + } + + // All demands in full trust domains are no-ops, so skip +#if FEATURE_CAS_POLICY + if (!CodeAccessSecurityEngine.QuickCheckForAllDemands()) +#endif // FEATURE_CAS_POLICY + { + // Build up security permissions required, as well as validate we + // have a sensible set of parameters. IE, creating a brand new file + // for reading doesn't make much sense. + FileIOPermissionAccess secAccess = FileIOPermissionAccess.NoAccess; + + if (read) + { + Contract.Assert(mode != FileMode.Append); + secAccess = secAccess | FileIOPermissionAccess.Read; + } + + // I can't think of any combos of FileMode we should disallow if we + // don't have read access. Writing would pretty much always be valid + // in those cases. + + // For any FileSystemRights other than ReadAndExecute, demand Write permission + // This is probably bit overkill for TakeOwnership etc but we don't have any + // matching FileIOPermissionAccess to demand. It is better that we ask for Write permission. + +#if FEATURE_MACL + // FileMode.OpenOrCreate & FileSystemRights.Synchronize can create 0-byte file; demand write + if ((!useRights && (access & FileAccess.Write) != 0) + || (useRights && (fileSystemRights & (FileSystemRights.Write | FileSystemRights.Delete + | FileSystemRights.DeleteSubdirectoriesAndFiles + | FileSystemRights.ChangePermissions + | FileSystemRights.TakeOwnership)) != 0) + || (useRights && ((fileSystemRights & FileSystemRights.Synchronize) != 0) + && mode==FileMode.OpenOrCreate) + ) +#else + if (!useRights && (access & FileAccess.Write) != 0) +#endif //FEATURE_MACL + { + if (mode==FileMode.Append) + secAccess = secAccess | FileIOPermissionAccess.Append; + else + secAccess = secAccess | FileIOPermissionAccess.Write; + } + +#if FEATURE_MACL + bool specifiedAcl; + unsafe { + specifiedAcl = secAttrs != null && secAttrs.pSecurityDescriptor != null; + } + + AccessControlActions control = specifiedAcl ? AccessControlActions.Change : AccessControlActions.None; + new FileIOPermission(secAccess, control, new String[] { filePath }, false, false).Demand(); +#else +#if FEATURE_CORECLR + if (checkHost) { + FileSecurityState state = new FileSecurityState(FileSecurityState.ToFileSecurityState(secAccess), path, filePath); + state.EnsureState(); + } +#else + new FileIOPermission(secAccess, new String[] { filePath }, false, false).Demand(); +#endif // FEATURE_CORECLR +#endif + } + + // Our Inheritable bit was stolen from Windows, but should be set in + // the security attributes class. Don't leave this bit set. + share &= ~FileShare.Inheritable; + + bool seekToEnd = (mode==FileMode.Append); + // Must use a valid Win32 constant here... + if (mode == FileMode.Append) + mode = FileMode.OpenOrCreate; + + // WRT async IO, do the right thing for whatever platform we're on. + // This way, someone can easily write code that opens a file + // asynchronously no matter what their platform is. + if ((options & FileOptions.Asynchronous) != 0) + _isAsync = true; + else + options &= ~FileOptions.Asynchronous; + + int flagsAndAttributes = (int) options; + +#if !PLATFORM_UNIX + // For mitigating local elevation of privilege attack through named pipes + // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the + // named pipe server can't impersonate a high privileged client security context + flagsAndAttributes |= (Win32Native.SECURITY_SQOS_PRESENT | Win32Native.SECURITY_ANONYMOUS); +#endif + + // Don't pop up a dialog for reading from an emtpy floppy drive + int oldMode = Win32Native.SetErrorMode(Win32Native.SEM_FAILCRITICALERRORS); + try { + String tempPath = filePath; + if (useLongPath) + tempPath = Path.AddLongPathPrefix(tempPath); + _handle = Win32Native.SafeCreateFile(tempPath, fAccess, share, secAttrs, mode, flagsAndAttributes, IntPtr.Zero); + + if (_handle.IsInvalid) { + // Return a meaningful exception, using the RELATIVE path to + // the file to avoid returning extra information to the caller + // unless they have path discovery permission, in which case + // the full path is fine & useful. + + // NT5 oddity - when trying to open "C:\" as a FileStream, + // we usually get ERROR_PATH_NOT_FOUND from the OS. We should + // probably be consistent w/ every other directory. + int errorCode = Marshal.GetLastWin32Error(); + if (errorCode==__Error.ERROR_PATH_NOT_FOUND && filePath.Equals(Directory.InternalGetDirectoryRoot(filePath))) + errorCode = __Error.ERROR_ACCESS_DENIED; + + // We need to give an exception, and preferably it would include + // the fully qualified path name. Do security check here. If + // we fail, give back the msgPath, which should not reveal much. + // While this logic is largely duplicated in + // __Error.WinIOError, we need this for + // IsolatedStorageFileStream. + bool canGiveFullPath = false; + + if (!bFromProxy) + { + try { +#if !FEATURE_CORECLR + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false ).Demand(); +#endif + canGiveFullPath = true; + } + catch(SecurityException) {} + } + + if (canGiveFullPath) + __Error.WinIOError(errorCode, _fileName); + else + __Error.WinIOError(errorCode, msgPath); + } + } + finally { + Win32Native.SetErrorMode(oldMode); + } + + // Disallow access to all non-file devices from the FileStream + // constructors that take a String. Everyone else can call + // CreateFile themselves then use the constructor that takes an + // IntPtr. Disallows "con:", "com1:", "lpt1:", etc. + int fileType = Win32Native.GetFileType(_handle); + if (fileType != Win32Native.FILE_TYPE_DISK) { + _handle.Close(); + throw new NotSupportedException(Environment.GetResourceString("NotSupported_FileStreamOnNonFiles")); + } + + // This is necessary for async IO using IO Completion ports via our + // managed Threadpool API's. This (theoretically) calls the OS's + // BindIoCompletionCallback method, and passes in a stub for the + // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped + // struct for this request and gets a delegate to a managed callback + // from there, which it then calls on a threadpool thread. (We allocate + // our native OVERLAPPED structs 2 pointers too large and store EE state + // & GC handles there, one to an IAsyncResult, the other to a delegate.) + if (_isAsync) { + bool b = false; + // BindHandle requires UnmanagedCode permission +#pragma warning disable 618 + new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert(); +#pragma warning restore 618 + try { + b = ThreadPool.BindHandle(_handle); + } + finally { + CodeAccessPermission.RevertAssert(); + if (!b) { + // We should close the handle so that the handle is not open until SafeFileHandle GC + Contract.Assert(!_exposedHandle, "Are we closing handle that we exposed/not own, how?"); + _handle.Close(); + } + } + if (!b) + throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed")); + } + + if (!useRights) { + _canRead = (access & FileAccess.Read) != 0; + _canWrite = (access & FileAccess.Write) != 0; + } +#if FEATURE_MACL + else { + _canRead = (fileSystemRights & FileSystemRights.ReadData) != 0; + _canWrite = ((fileSystemRights & FileSystemRights.WriteData) != 0) + || ((fileSystemRights & FileSystemRights.AppendData) != 0); + } +#endif //FEATURE_MACL + + _canSeek = true; + _isPipe = false; + _pos = 0; + _bufferSize = bufferSize; + _readPos = 0; + _readLen = 0; + _writePos = 0; + + // For Append mode... + if (seekToEnd) { + _appendStart = SeekCore(0, SeekOrigin.End); + } + else { + _appendStart = -1; + } + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access) + : this(handle, access, true, DefaultBufferSize, false) { + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle) + : this(handle, access, ownsHandle, DefaultBufferSize, false) { + } + + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize) + : this(handle, access, ownsHandle, bufferSize, false) { + } + + // We explicitly do a Demand, not a LinkDemand here. + [System.Security.SecuritySafeCritical] // auto-generated + [Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. http://go.microsoft.com/fwlink/?linkid=14202")] +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#pragma warning restore 618 + public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync) + : this(new SafeFileHandle(handle, ownsHandle), access, bufferSize, isAsync) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(SafeFileHandle handle, FileAccess access) + : this(handle, access, DefaultBufferSize, false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated + public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) + : this(handle, access, bufferSize, false) { + } + + [System.Security.SecuritySafeCritical] // auto-generated +#pragma warning disable 618 + [SecurityPermissionAttribute(SecurityAction.Demand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#pragma warning restore 618 + public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) { + // To ensure we don't leak a handle, put it in a SafeFileHandle first + if (handle.IsInvalid) + throw new ArgumentException(Environment.GetResourceString("Arg_InvalidHandle"), "handle"); + Contract.EndContractBlock(); + + _handle = handle; + _exposedHandle = true; + + // Now validate arguments. + if (access < FileAccess.Read || access > FileAccess.ReadWrite) + throw new ArgumentOutOfRangeException("access", Environment.GetResourceString("ArgumentOutOfRange_Enum")); + if (bufferSize <= 0) + throw new ArgumentOutOfRangeException("bufferSize", Environment.GetResourceString("ArgumentOutOfRange_NeedPosNum")); + + int handleType = Win32Native.GetFileType(_handle); + Contract.Assert(handleType == Win32Native.FILE_TYPE_DISK || handleType == Win32Native.FILE_TYPE_PIPE || handleType == Win32Native.FILE_TYPE_CHAR, "FileStream was passed an unknown file type!"); + _isAsync = isAsync; + _canRead = 0 != (access & FileAccess.Read); + _canWrite = 0 != (access & FileAccess.Write); + _canSeek = handleType == Win32Native.FILE_TYPE_DISK; + _bufferSize = bufferSize; + _readPos = 0; + _readLen = 0; + _writePos = 0; + _fileName = null; + _isPipe = handleType == Win32Native.FILE_TYPE_PIPE; + + // This is necessary for async IO using IO Completion ports via our + // managed Threadpool API's. This calls the OS's + // BindIoCompletionCallback method, and passes in a stub for the + // LPOVERLAPPED_COMPLETION_ROUTINE. This stub looks at the Overlapped + // struct for this request and gets a delegate to a managed callback + // from there, which it then calls on a threadpool thread. (We allocate + // our native OVERLAPPED structs 2 pointers too large and store EE + // state & a handle to a delegate there.) +#if !FEATURE_CORECLR + if (_isAsync) { + bool b = false; + try { + b = ThreadPool.BindHandle(_handle); + } + catch (ApplicationException) { + // If you passed in a synchronous handle and told us to use + // it asynchronously, throw here. + throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotAsync")); + } + if (!b) { + throw new IOException(Environment.GetResourceString("IO.IO_BindHandleFailed")); + } + } + else { +#endif // FEATURE_CORECLR + if (handleType != Win32Native.FILE_TYPE_PIPE) + VerifyHandleIsSync(); +#if !FEATURE_CORECLR + } +#endif // FEATURE_CORECLR + + if (_canSeek) + SeekCore(0, SeekOrigin.Current); + else + _pos = 0; + } + + [System.Security.SecuritySafeCritical] // auto-generated + private static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share) + { + Win32Native.SECURITY_ATTRIBUTES secAttrs = null; + if ((share & FileShare.Inheritable) != 0) { + secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); + + secAttrs.bInheritHandle = 1; + } + return secAttrs; + } + +#if FEATURE_MACL + // If pinningHandle is not null, caller must free it AFTER the call to + // CreateFile has returned. + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe static Win32Native.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share, FileSecurity fileSecurity, out Object pinningHandle) + { + pinningHandle = null; + Win32Native.SECURITY_ATTRIBUTES secAttrs = null; + if ((share & FileShare.Inheritable) != 0 || fileSecurity != null) { + secAttrs = new Win32Native.SECURITY_ATTRIBUTES(); + secAttrs.nLength = (int)Marshal.SizeOf(secAttrs); + + if ((share & FileShare.Inheritable) != 0) { + secAttrs.bInheritHandle = 1; + } + + // For ACL's, get the security descriptor from the FileSecurity. + if (fileSecurity != null) { + byte[] sd = fileSecurity.GetSecurityDescriptorBinaryForm(); + pinningHandle = GCHandle.Alloc(sd, GCHandleType.Pinned); + fixed(byte* pSecDescriptor = sd) + secAttrs.pSecurityDescriptor = pSecDescriptor; + } + } + return secAttrs; + } +#endif + + // Verifies that this handle supports synchronous IO operations (unless you + // didn't open it for either reading or writing). + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe void VerifyHandleIsSync() + { + // Do NOT use this method on pipes. Reading or writing to a pipe may + // cause an app to block incorrectly, introducing a deadlock (depending + // on whether a write will wake up an already-blocked thread or this + // FileStream's thread). + + // Do NOT change this to use a byte[] of length 0, or test test won't + // work. Our ReadFile & WriteFile methods are special cased to return + // for arrays of length 0, since we'd get an IndexOutOfRangeException + // while using C#'s fixed syntax. + byte[] bytes = new byte[1]; + int hr = 0; + int r = 0; + + // If the handle is a pipe, ReadFile will block until there + // has been a write on the other end. We'll just have to deal with it, + // For the read end of a pipe, you can mess up and + // accidentally read synchronously from an async pipe. + if (CanRead) { + r = ReadFileNative(_handle, bytes, 0, 0, null, out hr); + } + else if (CanWrite) { + r = WriteFileNative(_handle, bytes, 0, 0, null, out hr); + } + + if (hr==ERROR_INVALID_PARAMETER) + throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync")); + if (hr == Win32Native.ERROR_INVALID_HANDLE) + __Error.WinIOError(hr, "<OS handle>"); + } + + + public override bool CanRead { + [Pure] + get { return _canRead; } + } + + public override bool CanWrite { + [Pure] + get { return _canWrite; } + } + + public override bool CanSeek { + [Pure] + get { return _canSeek; } + } + + public virtual bool IsAsync { + get { return _isAsync; } + } + + public override long Length { + [System.Security.SecuritySafeCritical] // auto-generated + get { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + int hi = 0, lo = 0; + + lo = Win32Native.GetFileSize(_handle, out hi); + + if (lo==-1) { // Check for either an error or a 4GB - 1 byte file. + int hr = Marshal.GetLastWin32Error(); + if (hr != 0) + __Error.WinIOError(hr, String.Empty); + } + long len = (((long)hi) << 32) | ((uint) lo); + // If we're writing near the end of the file, we must include our + // internal buffer in our Length calculation. Don't flush because + // we use the length of the file in our async write method. + if (_writePos > 0 && _pos + _writePos > len) + len = _writePos + _pos; + return len; + } + } + + public String Name { + [System.Security.SecuritySafeCritical] + get { + if (_fileName == null) + return Environment.GetResourceString("IO_UnknownFileName"); +#if FEATURE_CORECLR + FileSecurityState sourceState = new FileSecurityState(FileSecurityStateAccess.PathDiscovery, String.Empty, _fileName); + sourceState.EnsureState(); +#else + new FileIOPermission(FileIOPermissionAccess.PathDiscovery, new String[] { _fileName }, false, false).Demand(); +#endif + return _fileName; + } + } + + internal String NameInternal { + get { + if (_fileName == null) + return "<UnknownFileName>"; + return _fileName; + } + } + + public override long Position { + [System.Security.SecuritySafeCritical] // auto-generated + get { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + + Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + // Verify that internal position is in sync with the handle + if (_exposedHandle) + VerifyOSHandlePosition(); + + // Compensate for buffer that we read from the handle (_readLen) Vs what the user + // read so far from the internel buffer (_readPos). Of course add any unwrittern + // buffered data + return _pos + (_readPos - _readLen + _writePos); + } + set { + if (value < 0) throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_writePos > 0) FlushWrite(false); + _readPos = 0; + _readLen = 0; + Seek(value, SeekOrigin.Begin); + } + } + +#if FEATURE_MACL + [System.Security.SecuritySafeCritical] // auto-generated + public FileSecurity GetAccessControl() + { + if (_handle.IsClosed) __Error.FileNotOpen(); + return new FileSecurity(_handle, _fileName, AccessControlSections.Access | AccessControlSections.Owner | AccessControlSections.Group); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public void SetAccessControl(FileSecurity fileSecurity) + { + if (fileSecurity == null) + throw new ArgumentNullException("fileSecurity"); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + fileSecurity.Persist(_handle, _fileName); + } +#endif + + [System.Security.SecuritySafeCritical] // auto-generated + protected override void Dispose(bool disposing) + { + // Nothing will be done differently based on whether we are + // disposing vs. finalizing. This is taking advantage of the + // weak ordering between normal finalizable objects & critical + // finalizable objects, which I included in the SafeHandle + // design for FileStream, which would often "just work" when + // finalized. + try { + if (_handle != null && !_handle.IsClosed) { + // Flush data to disk iff we were writing. After + // thinking about this, we also don't need to flush + // our read position, regardless of whether the handle + // was exposed to the user. They probably would NOT + // want us to do this. + if (_writePos > 0) { + FlushWrite(!disposing); + } + } + } + finally { + if (_handle != null && !_handle.IsClosed) + _handle.Dispose(); + + _canRead = false; + _canWrite = false; + _canSeek = false; + // Don't set the buffer to null, to avoid a NullReferenceException + // when users have a race condition in their code (ie, they call + // Close when calling another method on Stream like Read). + //_buffer = null; + base.Dispose(disposing); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + ~FileStream() + { + if (_handle != null) { + BCLDebug.Correctness(_handle.IsClosed, "You didn't close a FileStream & it got finalized. Name: \""+_fileName+"\""); + Dispose(false); + } + } + + public override void Flush() + { + Flush(false); + } + + [System.Security.SecuritySafeCritical] + public virtual void Flush(Boolean flushToDisk) + { + // This code is duplicated in Dispose + if (_handle.IsClosed) __Error.FileNotOpen(); + + FlushInternalBuffer(); + + if (flushToDisk && CanWrite) + { + FlushOSBuffer(); + } + } + + private void FlushInternalBuffer() + { + if (_writePos > 0) + { + FlushWrite(false); + } + else if (_readPos < _readLen && CanSeek) + { + FlushRead(); + } + } + + [System.Security.SecuritySafeCritical] + private void FlushOSBuffer() + { + if (!Win32Native.FlushFileBuffers(_handle)) + { + __Error.WinIOError(); + } + } + + // Reading is done by blocks from the file, but someone could read + // 1 byte from the buffer then write. At that point, the OS's file + // pointer is out of sync with the stream's position. All write + // functions should call this function to preserve the position in the file. + private void FlushRead() { + Contract.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushRead!"); + if (_readPos - _readLen != 0) { + Contract.Assert(CanSeek, "FileStream will lose buffered read data now."); + SeekCore(_readPos - _readLen, SeekOrigin.Current); + } + _readPos = 0; + _readLen = 0; + } + + // Writes are buffered. Anytime the buffer fills up + // (_writePos + delta > _bufferSize) or the buffer switches to reading + // and there is left over data (_writePos > 0), this function must be called. + private void FlushWrite(bool calledFromFinalizer) { + Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWrite!"); + + if (_isAsync) { + IAsyncResult asyncResult = BeginWriteCore(_buffer, 0, _writePos, null, null); + // With our Whidbey async IO & overlapped support for AD unloads, + // we don't strictly need to block here to release resources + // since that support takes care of the pinning & freeing the + // overlapped struct. We need to do this when called from + // Close so that the handle is closed when Close returns, but + // we do't need to call EndWrite from the finalizer. + // Additionally, if we do call EndWrite, we block forever + // because AD unloads prevent us from running the managed + // callback from the IO completion port. Blocking here when + // called from the finalizer during AD unload is clearly wrong, + // but we can't use any sort of test for whether the AD is + // unloading because if we weren't unloading, an AD unload + // could happen on a separate thread before we call EndWrite. + if (!calledFromFinalizer) + EndWrite(asyncResult); + } + else + WriteCore(_buffer, 0, _writePos); + + _writePos = 0; + } + + + [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. http://go.microsoft.com/fwlink/?linkid=14202")] + public virtual IntPtr Handle { + [System.Security.SecurityCritical] // auto-generated_required +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#endif + get { + Flush(); + // Explicitly dump any buffered data, since the user could move our + // position or write to the file. + _readPos = 0; + _readLen = 0; + _writePos = 0; + _exposedHandle = true; + + return _handle.DangerousGetHandle(); + } + } + + public virtual SafeFileHandle SafeFileHandle { + [System.Security.SecurityCritical] // auto-generated_required +#if !FEATURE_CORECLR + [SecurityPermissionAttribute(SecurityAction.InheritanceDemand, Flags=SecurityPermissionFlag.UnmanagedCode)] +#endif + get { + Flush(); + // Explicitly dump any buffered data, since the user could move our + // position or write to the file. + _readPos = 0; + _readLen = 0; + _writePos = 0; + _exposedHandle = true; + + return _handle; + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void SetLength(long value) + { + if (value < 0) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + if (!CanWrite) __Error.WriteNotSupported(); + + // Handle buffering updates. + if (_writePos > 0) { + FlushWrite(false); + } + else if (_readPos < _readLen) { + FlushRead(); + } + _readPos = 0; + _readLen = 0; + + if (_appendStart != -1 && value < _appendStart) + throw new IOException(Environment.GetResourceString("IO.IO_SetLengthAppendTruncate")); + SetLengthCore(value); + } + + // We absolutely need this method broken out so that BeginWriteCore can call + // a method without having to go through buffering code that might call + // FlushWrite. + [System.Security.SecuritySafeCritical] // auto-generated + private void SetLengthCore(long value) + { + Contract.Assert(value >= 0, "value >= 0"); + long origPos = _pos; + + if (_exposedHandle) + VerifyOSHandlePosition(); + if (_pos != value) + SeekCore(value, SeekOrigin.Begin); + if (!Win32Native.SetEndOfFile(_handle)) { + int hr = Marshal.GetLastWin32Error(); + if (hr==__Error.ERROR_INVALID_PARAMETER) + throw new ArgumentOutOfRangeException("value", Environment.GetResourceString("ArgumentOutOfRange_FileLengthTooBig")); + __Error.WinIOError(hr, String.Empty); + } + // Return file pointer to where it was before setting length + if (origPos != value) { + if (origPos < value) + SeekCore(origPos, SeekOrigin.Begin); + else + SeekCore(0, SeekOrigin.End); + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override int Read([In, Out] byte[] array, int offset, int count) { + if (array==null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + bool isBlocked = false; + int n = _readLen - _readPos; + // if the read buffer is empty, read into either user's array or our + // buffer, depending on number of bytes user asked for and buffer size. + if (n == 0) { + if (!CanRead) __Error.ReadNotSupported(); + if (_writePos > 0) FlushWrite(false); + if (!CanSeek || (count >= _bufferSize)) { + n = ReadCore(array, offset, count); + // Throw away read buffer. + _readPos = 0; + _readLen = 0; + return n; + } + if (_buffer == null) _buffer = new byte[_bufferSize]; + n = ReadCore(_buffer, 0, _bufferSize); + if (n == 0) return 0; + isBlocked = n < _bufferSize; + _readPos = 0; + _readLen = n; + } + // Now copy min of count or numBytesAvailable (ie, near EOF) to array. + if (n > count) n = count; + Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); + _readPos += n; + + // We may have read less than the number of bytes the user asked + // for, but that is part of the Stream contract. Reading again for + // more data may cause us to block if we're using a device with + // no clear end of file, such as a serial port or pipe. If we + // blocked here & this code was used with redirected pipes for a + // process's standard output, this can lead to deadlocks involving + // two processes. But leave this here for files to avoid what would + // probably be a breaking change. -- + + // If we are reading from a device with no clear EOF like a + // serial port or a pipe, this will cause us to block incorrectly. + if (!_isPipe) { + // If we hit the end of the buffer and didn't have enough bytes, we must + // read some more from the underlying stream. However, if we got + // fewer bytes from the underlying stream than we asked for (ie, we're + // probably blocked), don't ask for more bytes. + if (n < count && !isBlocked) { + Contract.Assert(_readPos == _readLen, "Read buffer should be empty!"); + int moreBytesRead = ReadCore(array, offset + n, count - n); + n += moreBytesRead; + // We've just made our buffer inconsistent with our position + // pointer. We must throw away the read buffer. + _readPos = 0; + _readLen = 0; + } + } + + return n; + } + + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe int ReadCore(byte[] buffer, int offset, int count) { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanRead, "CanRead"); + + Contract.Assert(buffer != null, "buffer != null"); + Contract.Assert(_writePos == 0, "_writePos == 0"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(count >= 0, "count is negative"); + + if (_isAsync) { + IAsyncResult result = BeginReadCore(buffer, offset, count, null, null, 0); + return EndRead(result); + } + + // Make sure we are reading from the right spot + if (_exposedHandle) + VerifyOSHandlePosition(); + + int hr = 0; + int r = ReadFileNative(_handle, buffer, offset, count, null, out hr); + if (r == -1) { + // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. + if (hr == ERROR_BROKEN_PIPE) { + r = 0; + } + else { + if (hr == ERROR_INVALID_PARAMETER) + throw new ArgumentException(Environment.GetResourceString("Arg_HandleNotSync")); + + __Error.WinIOError(hr, String.Empty); + } + } + Contract.Assert(r >= 0, "FileStream's ReadCore is likely broken."); + _pos += r; + + return r; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override long Seek(long offset, SeekOrigin origin) { + if (origin<SeekOrigin.Begin || origin>SeekOrigin.End) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidSeekOrigin")); + Contract.EndContractBlock(); + if (_handle.IsClosed) __Error.FileNotOpen(); + if (!CanSeek) __Error.SeekNotSupported(); + + Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + // If we've got bytes in our buffer to write, write them out. + // If we've read in and consumed some bytes, we'll have to adjust + // our seek positions ONLY IF we're seeking relative to the current + // position in the stream. This simulates doing a seek to the new + // position, then a read for the number of bytes we have in our buffer. + if (_writePos > 0) { + FlushWrite(false); + } + else if (origin == SeekOrigin.Current) { + // Don't call FlushRead here, which would have caused an infinite + // loop. Simply adjust the seek origin. This isn't necessary + // if we're seeking relative to the beginning or end of the stream. + offset -= (_readLen - _readPos); + } + + // Verify that internal position is in sync with the handle + if (_exposedHandle) + VerifyOSHandlePosition(); + + long oldPos = _pos + (_readPos - _readLen); + long pos = SeekCore(offset, origin); + + // Prevent users from overwriting data in a file that was opened in + // append mode. + if (_appendStart != -1 && pos < _appendStart) { + SeekCore(oldPos, SeekOrigin.Begin); + throw new IOException(Environment.GetResourceString("IO.IO_SeekAppendOverwrite")); + } + + // We now must update the read buffer. We can in some cases simply + // update _readPos within the buffer, copy around the buffer so our + // Position property is still correct, and avoid having to do more + // reads from the disk. Otherwise, discard the buffer's contents. + if (_readLen > 0) { + // We can optimize the following condition: + // oldPos - _readPos <= pos < oldPos + _readLen - _readPos + if (oldPos == pos) { + if (_readPos > 0) { + //Console.WriteLine("Seek: seeked for 0, adjusting buffer back by: "+_readPos+" _readLen: "+_readLen); + Buffer.InternalBlockCopy(_buffer, _readPos, _buffer, 0, _readLen - _readPos); + _readLen -= _readPos; + _readPos = 0; + } + // If we still have buffered data, we must update the stream's + // position so our Position property is correct. + if (_readLen > 0) + SeekCore(_readLen, SeekOrigin.Current); + } + else if (oldPos - _readPos < pos && pos < oldPos + _readLen - _readPos) { + int diff = (int)(pos - oldPos); + //Console.WriteLine("Seek: diff was "+diff+", readpos was "+_readPos+" adjusting buffer - shrinking by "+ (_readPos + diff)); + Buffer.InternalBlockCopy(_buffer, _readPos+diff, _buffer, 0, _readLen - (_readPos + diff)); + _readLen -= (_readPos + diff); + _readPos = 0; + if (_readLen > 0) + SeekCore(_readLen, SeekOrigin.Current); + } + else { + // Lose the read buffer. + _readPos = 0; + _readLen = 0; + } + Contract.Assert(_readLen >= 0 && _readPos <= _readLen, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen"); + Contract.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled."); + } + return pos; + } + + // This doesn't do argument checking. Necessary for SetLength, which must + // set the file pointer beyond the end of the file. This will update the + // internal position + [System.Security.SecuritySafeCritical] // auto-generated + private long SeekCore(long offset, SeekOrigin origin) { + Contract.Assert(!_handle.IsClosed && CanSeek, "!_handle.IsClosed && CanSeek"); + Contract.Assert(origin>=SeekOrigin.Begin && origin<=SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End"); + int hr = 0; + long ret = 0; + + ret = Win32Native.SetFilePointer(_handle, offset, origin, out hr); + if (ret == -1) { + // #errorInvalidHandle + // If ERROR_INVALID_HANDLE is returned, it doesn't suffice to set + // the handle as invalid; the handle must also be closed. + // + // Marking the handle as invalid but not closing the handle + // resulted in exceptions during finalization and locked column + // values (due to invalid but unclosed handle) in SQL FileStream + // scenarios. + // + // A more mainstream scenario involves accessing a file on a + // network share. ERROR_INVALID_HANDLE may occur because the network + // connection was dropped and the server closed the handle. However, + // the client side handle is still open and even valid for certain + // operations. + // + // Note that Dispose doesn't throw so we don't need to special case. + // SetHandleAsInvalid only sets _closed field to true (without + // actually closing handle) so we don't need to call that as well. + if (hr == Win32Native.ERROR_INVALID_HANDLE) + _handle.Dispose(); + __Error.WinIOError(hr, String.Empty); + } + + _pos = ret; + return ret; + } + + // Checks the position of the OS's handle equals what we expect it to. + // This will fail if someone else moved the FileStream's handle or if + // we've hit a bug in FileStream's position updating code. + private void VerifyOSHandlePosition() + { + if (!CanSeek) + return; + + // SeekCore will override the current _pos, so save it now + long oldPos = _pos; + long curPos = SeekCore(0, SeekOrigin.Current); + + if (curPos != oldPos) { + // For reads, this is non-fatal but we still could have returned corrupted + // data in some cases. So discard the internal buffer. Potential MDA + _readPos = 0; + _readLen = 0; + if(_writePos > 0) { + // Discard the buffer and let the user know! + _writePos = 0; + throw new IOException(Environment.GetResourceString("IO.IO_FileStreamHandlePosition")); + } + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void Write(byte[] array, int offset, int count) { + if (array==null) + throw new ArgumentNullException("array", Environment.GetResourceString("ArgumentNull_Buffer")); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + if (_writePos == 0) + { + // Ensure we can write to the stream, and ready buffer for writing. + if (!CanWrite) __Error.WriteNotSupported(); + if (_readPos < _readLen) FlushRead(); + _readPos = 0; + _readLen = 0; + } + + // If our buffer has data in it, copy data from the user's array into + // the buffer, and if we can fit it all there, return. Otherwise, write + // the buffer to disk and copy any remaining data into our buffer. + // The assumption here is memcpy is cheaper than disk (or net) IO. + // (10 milliseconds to disk vs. ~20-30 microseconds for a 4K memcpy) + // So the extra copying will reduce the total number of writes, in + // non-pathological cases (ie, write 1 byte, then write for the buffer + // size repeatedly) + if (_writePos > 0) { + int numBytes = _bufferSize - _writePos; // space left in buffer + if (numBytes > 0) { + if (numBytes > count) + numBytes = count; + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes); + _writePos += numBytes; + if (count==numBytes) return; + offset += numBytes; + count -= numBytes; + } + // Reset our buffer. We essentially want to call FlushWrite + // without calling Flush on the underlying Stream. + + if (_isAsync) { + IAsyncResult result = BeginWriteCore(_buffer, 0, _writePos, null, null); + EndWrite(result); + } + else + { + WriteCore(_buffer, 0, _writePos); + } + + _writePos = 0; + } + // If the buffer would slow writes down, avoid buffer completely. + if (count >= _bufferSize) { + Contract.Assert(_writePos == 0, "FileStream cannot have buffered data to write here! Your stream will be corrupted."); + WriteCore(array, offset, count); + return; + } + else if (count == 0) + return; // Don't allocate a buffer then call memcpy for 0 bytes. + if (_buffer==null) _buffer = new byte[_bufferSize]; + // Copy remaining bytes into buffer, to write at a later date. + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, count); + _writePos = count; + return; + } + + [System.Security.SecuritySafeCritical] // auto-generated + private unsafe void WriteCore(byte[] buffer, int offset, int count) { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanWrite, "CanWrite"); + + Contract.Assert(buffer != null, "buffer != null"); + Contract.Assert(_readPos == _readLen, "_readPos == _readLen"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(count >= 0, "count is negative"); + + if (_isAsync) { + IAsyncResult result = BeginWriteCore(buffer, offset, count, null, null); + EndWrite(result); + return; + } + + // Make sure we are writing to the position that we think we are + if (_exposedHandle) + VerifyOSHandlePosition(); + + int hr = 0; + int r = WriteFileNative(_handle, buffer, offset, count, null, out hr); + if (r == -1) { + // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. + if (hr == ERROR_NO_DATA) { + r = 0; + } + else { + // ERROR_INVALID_PARAMETER may be returned for writes + // where the position is too large (ie, writing at Int64.MaxValue + // on Win9x) OR for synchronous writes to a handle opened + // asynchronously. + if (hr == ERROR_INVALID_PARAMETER) + throw new IOException(Environment.GetResourceString("IO.IO_FileTooLongOrHandleNotSync")); + __Error.WinIOError(hr, String.Empty); + } + } + Contract.Assert(r >= 0, "FileStream's WriteCore is likely broken."); + _pos += r; + return; + } + + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading = true)] + public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + if (array==null) + throw new ArgumentNullException("array"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (numBytes < 0) + throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < numBytes) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + if (!_isAsync) + return base.BeginRead(array, offset, numBytes, userCallback, stateObject); + else + return BeginReadAsync(array, offset, numBytes, userCallback, stateObject); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading = true)] + private FileStreamAsyncResult BeginReadAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + Contract.Assert(_isAsync); + + if (!CanRead) __Error.ReadNotSupported(); + + Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + if (_isPipe) + { + // When redirecting stdout & stderr with the Process class, it's easy to deadlock your + // parent & child processes when doing writes 4K at a time. The + // OS appears to use a 4K buffer internally. If you write to a + // pipe that is full, you will block until someone read from + // that pipe. If you try reading from an empty pipe and + // FileStream's BeginRead blocks waiting for data to fill it's + // internal buffer, you will be blocked. In a case where a child + // process writes to stdout & stderr while a parent process tries + // reading from both, you can easily get into a deadlock here. + // To avoid this deadlock, don't buffer when doing async IO on + // pipes. But don't completely ignore buffered data either. + if (_readPos < _readLen) + { + int n = _readLen - _readPos; + if (n > numBytes) n = numBytes; + Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); + _readPos += n; + + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); + } + else + { + Contract.Assert(_writePos == 0, "FileStream must not have buffered write data here! Pipes should be unidirectional."); + return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0); + } + } + + Contract.Assert(!_isPipe, "Should not be a pipe."); + + // Handle buffering. + if (_writePos > 0) FlushWrite(false); + if (_readPos == _readLen) + { + // I can't see how to handle buffering of async requests when + // filling the buffer asynchronously, without a lot of complexity. + // The problems I see are issuing an async read, we do an async + // read to fill the buffer, then someone issues another read + // (either synchronously or asynchronously) before the first one + // returns. This would involve some sort of complex buffer locking + // that we probably don't want to get into, at least not in V1. + // If we did a sync read to fill the buffer, we could avoid the + // problem, and any async read less than 64K gets turned into a + // synchronous read by NT anyways... -- + + if (numBytes < _bufferSize) + { + if (_buffer == null) _buffer = new byte[_bufferSize]; + IAsyncResult bufferRead = BeginReadCore(_buffer, 0, _bufferSize, null, null, 0); + _readLen = EndRead(bufferRead); + int n = _readLen; + if (n > numBytes) n = numBytes; + Buffer.InternalBlockCopy(_buffer, 0, array, offset, n); + _readPos = n; + + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); + } + else + { + // Here we're making our position pointer inconsistent + // with our read buffer. Throw away the read buffer's contents. + _readPos = 0; + _readLen = 0; + return BeginReadCore(array, offset, numBytes, userCallback, stateObject, 0); + } + } + else + { + int n = _readLen - _readPos; + if (n > numBytes) n = numBytes; + Buffer.InternalBlockCopy(_buffer, _readPos, array, offset, n); + _readPos += n; + + if (n >= numBytes) + { + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(n, userCallback, stateObject, false); + } + else + { + // For streams with no clear EOF like serial ports or pipes + // we cannot read more data without causing an app to block + // incorrectly. Pipes don't go down this path + // though. This code needs to be fixed. + // Throw away read buffer. + _readPos = 0; + _readLen = 0; + return BeginReadCore(array, offset + n, numBytes - n, userCallback, stateObject, n); + } + // WARNING: all state on asyncResult objects must be set before + // we call ReadFile in BeginReadCore, since the OS can run our + // callback & the user's callback before ReadFile returns. + } + } + + [System.Security.SecuritySafeCritical] // auto-generated + unsafe private FileStreamAsyncResult BeginReadCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject, int numBufferedBytesRead) + { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanRead, "CanRead"); + Contract.Assert(bytes != null, "bytes != null"); + Contract.Assert(_writePos == 0, "_writePos == 0"); + Contract.Assert(_isAsync, "BeginReadCore doesn't work on synchronous file streams!"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(numBytes >= 0, "numBytes is negative"); + + // Create and store async stream class library specific data in the async result + + // Must pass in _numBufferedBytes here to ensure all the state on the IAsyncResult + // object is set before we call ReadFile, which gives the OS an + // opportunity to run our callback (including the user callback & + // the call to EndRead) before ReadFile has returned. + FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(numBufferedBytesRead, bytes, _handle, userCallback, stateObject, false); + NativeOverlapped* intOverlapped = asyncResult.OverLapped; + + // Calculate position in the file we should be at after the read is done + if (CanSeek) { + long len = Length; + + // Make sure we are reading from the position that we think we are + if (_exposedHandle) + VerifyOSHandlePosition(); + + if (_pos + numBytes > len) { + if (_pos <= len) + numBytes = (int) (len - _pos); + else + numBytes = 0; + } + + // Now set the position to read from in the NativeOverlapped struct + // For pipes, we should leave the offset fields set to 0. + intOverlapped->OffsetLow = unchecked((int)_pos); + intOverlapped->OffsetHigh = (int)(_pos>>32); + + // When using overlapped IO, the OS is not supposed to + // touch the file pointer location at all. We will adjust it + // ourselves. This isn't threadsafe. + + // WriteFile should not update the file pointer when writing + // in overlapped mode, according to MSDN. But it does update + // the file pointer when writing to a UNC path! + // So changed the code below to seek to an absolute + // location, not a relative one. ReadFile seems consistent though. + SeekCore(numBytes, SeekOrigin.Current); + } + + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false); + + // queue an async ReadFile operation and pass in a packed overlapped + int hr = 0; + int r = ReadFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); + // ReadFile, the OS version, will return 0 on failure. But + // my ReadFileNative wrapper returns -1. My wrapper will return + // the following: + // On error, r==-1. + // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING + // on async requests that completed sequentially, r==0 + // You will NEVER RELIABLY be able to get the number of bytes + // read back from this call when using overlapped structures! You must + // not pass in a non-null lpNumBytesRead to ReadFile when using + // overlapped structures! This is by design NT behavior. + if (r==-1 && numBytes!=-1) { + + // For pipes, when they hit EOF, they will come here. + if (hr == ERROR_BROKEN_PIPE) { + // Not an error, but EOF. AsyncFSCallback will NOT be + // called. Call the user callback here. + + // We clear the overlapped status bit for this special case. + // Failure to do so looks like we are freeing a pending overlapped later. + intOverlapped->InternalLow = IntPtr.Zero; + asyncResult.CallUserCallback(); + // EndRead will free the Overlapped struct correctly. + } + else if (hr != ERROR_IO_PENDING) { + if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. + SeekCore(0, SeekOrigin.Current); + + if (hr == ERROR_HANDLE_EOF) + __Error.EndOfFile(); + else + __Error.WinIOError(hr, String.Empty); + } + } + else { + // Due to a workaround for a race condition in NT's ReadFile & + // WriteFile routines, we will always be returning 0 from ReadFileNative + // when we do async IO instead of the number of bytes read, + // irregardless of whether the operation completed + // synchronously or asynchronously. We absolutely must not + // set asyncResult._numBytes here, since will never have correct + // results. + //Console.WriteLine("ReadFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on a separate thread"); + } + + return asyncResult; + } + + [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed. + public unsafe override int EndRead(IAsyncResult asyncResult) + { + // There are 3 significantly different IAsyncResults we'll accept + // here. One is from Stream::BeginRead. The other two are variations + // on our FileStreamAsyncResult. One is from BeginReadCore, + // while the other is from the BeginRead buffering wrapper. + if (asyncResult==null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + if (!_isAsync) + return base.EndRead(asyncResult); + + FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult; + if (afsar==null || afsar.IsWrite) + __Error.WrongAsyncResult(); + + // Ensure we don't have any race conditions by doing an interlocked + // CompareExchange here. Avoids corrupting memory via freeing the + // NativeOverlapped class or GCHandle twice. -- + if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0)) + __Error.EndReadCalledTwice(); + + // Obtain the WaitHandle, but don't use public property in case we + // delay initialize the manual reset event in the future. + afsar.Wait(); + + // Free memory & GC handles. + afsar.ReleaseNativeResource(); + + // Now check for any error during the read. + if (afsar.ErrorCode != 0) + __Error.WinIOError(afsar.ErrorCode, String.Empty); + + return afsar.NumBytesRead; + } + + // Reads a byte from the file stream. Returns the byte cast to an int + // or -1 if reading from the end of the stream. + [System.Security.SecuritySafeCritical] // auto-generated + public override int ReadByte() { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (_readLen==0 && !CanRead) __Error.ReadNotSupported(); + Contract.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + if (_readPos == _readLen) { + if (_writePos > 0) FlushWrite(false); + Contract.Assert(_bufferSize > 0, "_bufferSize > 0"); + if (_buffer == null) _buffer = new byte[_bufferSize]; + _readLen = ReadCore(_buffer, 0, _bufferSize); + _readPos = 0; + } + if (_readPos == _readLen) + return -1; + + int result = _buffer[_readPos]; + _readPos++; + return result; + } + + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading=true)] + public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + if (array==null) + throw new ArgumentNullException("array"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (numBytes < 0) + throw new ArgumentOutOfRangeException("numBytes", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (array.Length - offset < numBytes) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + if (_handle.IsClosed) __Error.FileNotOpen(); + + if (!_isAsync) + return base.BeginWrite(array, offset, numBytes, userCallback, stateObject); + else + return BeginWriteAsync(array, offset, numBytes, userCallback, stateObject); + } + + [System.Security.SecuritySafeCritical] // auto-generated + [HostProtection(ExternalThreading = true)] + private FileStreamAsyncResult BeginWriteAsync(byte[] array, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + Contract.Assert(_isAsync); + + if (!CanWrite) __Error.WriteNotSupported(); + + Contract.Assert((_readPos == 0 && _readLen == 0 && _writePos >= 0) || (_writePos == 0 && _readPos <= _readLen), "We're either reading or writing, but not both."); + + if (_isPipe) + { + // When redirecting stdout & stderr with the Process class, it's easy to deadlock your + // parent & child processes when doing writes 4K at a time. The + // OS appears to use a 4K buffer internally. If you write to a + // pipe that is full, you will block until someone read from + // that pipe. If you try reading from an empty pipe and + // FileStream's BeginRead blocks waiting for data to fill it's + // internal buffer, you will be blocked. In a case where a child + // process writes to stdout & stderr while a parent process tries + // reading from both, you can easily get into a deadlock here. + // To avoid this deadlock, don't buffer when doing async IO on + // pipes. + Contract.Assert(_readPos == 0 && _readLen == 0, "FileStream must not have buffered data here! Pipes should be unidirectional."); + + if (_writePos > 0) + FlushWrite(false); + + return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); + } + + // Handle buffering. + if (_writePos == 0) + { + if (_readPos < _readLen) FlushRead(); + _readPos = 0; + _readLen = 0; + } + + int n = _bufferSize - _writePos; + if (numBytes <= n) + { + if (_writePos == 0) _buffer = new byte[_bufferSize]; + Buffer.InternalBlockCopy(array, offset, _buffer, _writePos, numBytes); + _writePos += numBytes; + + // Return a synchronous FileStreamAsyncResult + return FileStreamAsyncResult.CreateBufferedReadResult(numBytes, userCallback, stateObject, true); + } + + if (_writePos > 0) + FlushWrite(false); + + return BeginWriteCore(array, offset, numBytes, userCallback, stateObject); + } + + [System.Security.SecuritySafeCritical] // auto-generated + unsafe private FileStreamAsyncResult BeginWriteCore(byte[] bytes, int offset, int numBytes, AsyncCallback userCallback, Object stateObject) + { + Contract.Assert(!_handle.IsClosed, "!_handle.IsClosed"); + Contract.Assert(CanWrite, "CanWrite"); + Contract.Assert(bytes != null, "bytes != null"); + Contract.Assert(_readPos == _readLen, "_readPos == _readLen"); + Contract.Assert(_isAsync, "BeginWriteCore doesn't work on synchronous file streams!"); + Contract.Assert(offset >= 0, "offset is negative"); + Contract.Assert(numBytes >= 0, "numBytes is negative"); + + // Create and store async stream class library specific data in the async result + FileStreamAsyncResult asyncResult = new FileStreamAsyncResult(0, bytes, _handle, userCallback, stateObject, true); + NativeOverlapped* intOverlapped = asyncResult.OverLapped; + + if (CanSeek) { + // Make sure we set the length of the file appropriately. + long len = Length; + //Console.WriteLine("BeginWrite - Calculating end pos. pos: "+pos+" len: "+len+" numBytes: "+numBytes); + + // Make sure we are writing to the position that we think we are + if (_exposedHandle) + VerifyOSHandlePosition(); + + if (_pos + numBytes > len) { + //Console.WriteLine("BeginWrite - Setting length to: "+(pos + numBytes)); + SetLengthCore(_pos + numBytes); + } + + // Now set the position to read from in the NativeOverlapped struct + // For pipes, we should leave the offset fields set to 0. + intOverlapped->OffsetLow = (int)_pos; + intOverlapped->OffsetHigh = (int)(_pos>>32); + + // When using overlapped IO, the OS is not supposed to + // touch the file pointer location at all. We will adjust it + // ourselves. This isn't threadsafe. + + SeekCore(numBytes, SeekOrigin.Current); + } + + //Console.WriteLine("BeginWrite finishing. pos: "+pos+" numBytes: "+numBytes+" _pos: "+_pos+" Position: "+Position); + + if (FrameworkEventSource.IsInitialized && FrameworkEventSource.Log.IsEnabled(EventLevel.Informational, FrameworkEventSource.Keywords.ThreadTransfer)) + FrameworkEventSource.Log.ThreadTransferSend((long)(asyncResult.OverLapped), 2, string.Empty, false); + + int hr = 0; + // queue an async WriteFile operation and pass in a packed overlapped + int r = WriteFileNative(_handle, bytes, offset, numBytes, intOverlapped, out hr); + + // WriteFile, the OS version, will return 0 on failure. But + // my WriteFileNative wrapper returns -1. My wrapper will return + // the following: + // On error, r==-1. + // On async requests that are still pending, r==-1 w/ hr==ERROR_IO_PENDING + // On async requests that completed sequentially, r==0 + // You will NEVER RELIABLY be able to get the number of bytes + // written back from this call when using overlapped IO! You must + // not pass in a non-null lpNumBytesWritten to WriteFile when using + // overlapped structures! This is ByDesign NT behavior. + if (r==-1 && numBytes!=-1) { + //Console.WriteLine("WriteFile returned 0; Write will complete asynchronously (if hr==3e5) hr: 0x{0:x}", hr); + + // For pipes, when they are closed on the other side, they will come here. + if (hr == ERROR_NO_DATA) { + // Not an error, but EOF. AsyncFSCallback will NOT be + // called. Call the user callback here. + asyncResult.CallUserCallback(); + // EndWrite will free the Overlapped struct correctly. + } + else if (hr != ERROR_IO_PENDING) { + if (!_handle.IsClosed && CanSeek) // Update Position - It could be anywhere. + SeekCore(0, SeekOrigin.Current); + + if (hr == ERROR_HANDLE_EOF) + __Error.EndOfFile(); + else + __Error.WinIOError(hr, String.Empty); + } + } + else { + // Due to a workaround for a race condition in NT's ReadFile & + // WriteFile routines, we will always be returning 0 from WriteFileNative + // when we do async IO instead of the number of bytes written, + // irregardless of whether the operation completed + // synchronously or asynchronously. We absolutely must not + // set asyncResult._numBytes here, since will never have correct + // results. + //Console.WriteLine("WriteFile returned: "+r+" (0x"+Int32.Format(r, "x")+") The IO completed synchronously, but the user callback was called on another thread."); + } + + return asyncResult; + } + + [System.Security.SecuritySafeCritical] // Although the unsafe code is only required in PAL, the block is wide scoped. Leave it here for desktop to ensure it's reviewed. + public unsafe override void EndWrite(IAsyncResult asyncResult) + { + if (asyncResult==null) + throw new ArgumentNullException("asyncResult"); + Contract.EndContractBlock(); + + if (!_isAsync) { + base.EndWrite(asyncResult); + return; + } + + FileStreamAsyncResult afsar = asyncResult as FileStreamAsyncResult; + if (afsar==null || !afsar.IsWrite) + __Error.WrongAsyncResult(); + + // Ensure we can't have any race conditions by doing an interlocked + // CompareExchange here. Avoids corrupting memory via freeing the + // NativeOverlapped class or GCHandle twice. -- + if (1 == Interlocked.CompareExchange(ref afsar._EndXxxCalled, 1, 0)) + __Error.EndWriteCalledTwice(); + + // Obtain the WaitHandle, but don't use public property in case we + // delay initialize the manual reset event in the future. + afsar.Wait(); + + // Free memory & GC handles. + afsar.ReleaseNativeResource(); + + // Now check for any error during the write. + if (afsar.ErrorCode != 0) + __Error.WinIOError(afsar.ErrorCode, String.Empty); + + // Number of bytes written is afsar._numBytes + afsar._numBufferedBytes. + return; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public override void WriteByte(byte value) + { + if (_handle.IsClosed) __Error.FileNotOpen(); + if (_writePos==0) { + if (!CanWrite) __Error.WriteNotSupported(); + if (_readPos < _readLen) FlushRead(); + _readPos = 0; + _readLen = 0; + Contract.Assert(_bufferSize > 0, "_bufferSize > 0"); + if (_buffer==null) _buffer = new byte[_bufferSize]; + } + if (_writePos == _bufferSize) + FlushWrite(false); + + _buffer[_writePos] = value; + _writePos++; + } + + [System.Security.SecuritySafeCritical] // auto-generated + public virtual void Lock(long position, long length) { + if (position < 0 || length < 0) + throw new ArgumentOutOfRangeException((position < 0 ? "position" : "length"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_handle.IsClosed) __Error.FileNotOpen(); + + int positionLow = unchecked((int)(position )); + int positionHigh = unchecked((int)(position >> 32)); + int lengthLow = unchecked((int)(length )); + int lengthHigh = unchecked((int)(length >> 32)); + + if (!Win32Native.LockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh)) + __Error.WinIOError(); + } + + [System.Security.SecuritySafeCritical] // auto-generated + public virtual void Unlock(long position, long length) { + if (position < 0 || length < 0) + throw new ArgumentOutOfRangeException((position < 0 ? "position" : "length"), Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + Contract.EndContractBlock(); + if (_handle.IsClosed) __Error.FileNotOpen(); + + int positionLow = unchecked((int)(position )); + int positionHigh = unchecked((int)(position >> 32)); + int lengthLow = unchecked((int)(length )); + int lengthHigh = unchecked((int)(length >> 32)); + + if (!Win32Native.UnlockFile(_handle, positionLow, positionHigh, lengthLow, lengthHigh)) + __Error.WinIOError(); + } + + // Windows API definitions, from winbase.h and others + + private const int FILE_ATTRIBUTE_NORMAL = 0x00000080; + private const int FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; + private const int FILE_FLAG_OVERLAPPED = 0x40000000; + internal const int GENERIC_READ = unchecked((int)0x80000000); + private const int GENERIC_WRITE = 0x40000000; + + private const int FILE_BEGIN = 0; + private const int FILE_CURRENT = 1; + private const int FILE_END = 2; + + // Error codes (not HRESULTS), from winerror.h + internal const int ERROR_BROKEN_PIPE = 109; + internal const int ERROR_NO_DATA = 232; + private const int ERROR_HANDLE_EOF = 38; + private const int ERROR_INVALID_PARAMETER = 87; + private const int ERROR_IO_PENDING = 997; + + + // __ConsoleStream also uses this code. + [System.Security.SecurityCritical] // auto-generated + private unsafe int ReadFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) + { + Contract.Requires(handle != null, "handle != null"); + Contract.Requires(offset >= 0, "offset >= 0"); + Contract.Requires(count >= 0, "count >= 0"); + Contract.Requires(bytes != null, "bytes != null"); + // Don't corrupt memory when multiple threads are erroneously writing + // to this stream simultaneously. + if (bytes.Length - offset < count) + throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition")); + Contract.EndContractBlock(); + + Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter mismatch in call to ReadFileNative."); + + // You can't use the fixed statement on an array of length 0. + if (bytes.Length==0) { + hr = 0; + return 0; + } + + int r = 0; + int numBytesRead = 0; + + fixed(byte* p = bytes) { + if (_isAsync) + r = Win32Native.ReadFile(handle, p + offset, count, IntPtr.Zero, overlapped); + else + r = Win32Native.ReadFile(handle, p + offset, count, out numBytesRead, IntPtr.Zero); + } + + if (r==0) { + hr = Marshal.GetLastWin32Error(); + // We should never silently drop an error here without some + // extra work. We must make sure that BeginReadCore won't return an + // IAsyncResult that will cause EndRead to block, since the OS won't + // call AsyncFSCallback for us. + if (hr == ERROR_BROKEN_PIPE || hr == Win32Native.ERROR_PIPE_NOT_CONNECTED) { + // This handle was a pipe, and it's done. Not an error, but EOF. + // However, the OS will not call AsyncFSCallback! + // Let the caller handle this, since BeginReadCore & ReadCore + // need to do different things. + return -1; + } + + // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)". + if (hr == Win32Native.ERROR_INVALID_HANDLE) + _handle.Dispose(); + + return -1; + } + else + hr = 0; + return numBytesRead; + } + + [System.Security.SecurityCritical] // auto-generated + private unsafe int WriteFileNative(SafeFileHandle handle, byte[] bytes, int offset, int count, NativeOverlapped* overlapped, out int hr) { + Contract.Requires(handle != null, "handle != null"); + Contract.Requires(offset >= 0, "offset >= 0"); + Contract.Requires(count >= 0, "count >= 0"); + Contract.Requires(bytes != null, "bytes != null"); + // Don't corrupt memory when multiple threads are erroneously writing + // to this stream simultaneously. (the OS is reading from + // the array we pass to WriteFile, but if we read beyond the end and + // that memory isn't allocated, we could get an AV.) + if (bytes.Length - offset < count) + throw new IndexOutOfRangeException(Environment.GetResourceString("IndexOutOfRange_IORaceCondition")); + Contract.EndContractBlock(); + + Contract.Assert((_isAsync && overlapped != null) || (!_isAsync && overlapped == null), "Async IO parameter missmatch in call to WriteFileNative."); + + // You can't use the fixed statement on an array of length 0. + if (bytes.Length==0) { + hr = 0; + return 0; + } + + int numBytesWritten = 0; + int r = 0; + + fixed(byte* p = bytes) { + if (_isAsync) + r = Win32Native.WriteFile(handle, p + offset, count, IntPtr.Zero, overlapped); + else + r = Win32Native.WriteFile(handle, p + offset, count, out numBytesWritten, IntPtr.Zero); + } + + if (r==0) { + hr = Marshal.GetLastWin32Error(); + // We should never silently drop an error here without some + // extra work. We must make sure that BeginWriteCore won't return an + // IAsyncResult that will cause EndWrite to block, since the OS won't + // call AsyncFSCallback for us. + + if (hr==ERROR_NO_DATA) { + // This handle was a pipe, and the pipe is being closed on the + // other side. Let the caller handle this, since BeginWriteCore + // & WriteCore need to do different things. + return -1; + } + + // See code:#errorInvalidHandle in "private long SeekCore(long offset, SeekOrigin origin)". + if (hr == Win32Native.ERROR_INVALID_HANDLE) + _handle.Dispose(); + + return -1; + } + else + hr = 0; + return numBytesWritten; + } + + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + [SecuritySafeCritical] + public override Task<int> ReadAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Read() or BeginRead() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Read/BeginRead) when we are not sure. + if (this.GetType() != typeof(FileStream)) + return base.ReadAsync(buffer, offset, count, cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled<int>(cancellationToken); + + if (_handle.IsClosed) + __Error.FileNotOpen(); + + // If async IO is not supported on this platform or + // if this FileStream was not opened with FileOptions.Asynchronous. + if (!_isAsync) + return base.ReadAsync(buffer, offset, count, cancellationToken); + + var readTask = new FileStreamReadWriteTask<int>(cancellationToken); + var endReadTask = s_endReadTask; + if (endReadTask == null) s_endReadTask = endReadTask = EndReadTask; // benign initialization race condition + readTask._asyncResult = BeginReadAsync(buffer, offset, count, endReadTask, readTask); + + if (readTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled) + { + var cancelReadHandler = s_cancelReadHandler; + if (cancelReadHandler == null) s_cancelReadHandler = cancelReadHandler = CancelTask<int>; // benign initialization race condition + readTask._registration = cancellationToken.Register(cancelReadHandler, readTask); + + // In case the task is completed right before we register the cancellation callback. + if (readTask._asyncResult.IsCompleted) + readTask._registration.Dispose(); + } + + return readTask; + } + + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + [SecuritySafeCritical] + public override Task WriteAsync(Byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + if (buffer == null) + throw new ArgumentNullException("buffer"); + if (offset < 0) + throw new ArgumentOutOfRangeException("offset", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (count < 0) + throw new ArgumentOutOfRangeException("count", Environment.GetResourceString("ArgumentOutOfRange_NeedNonNegNum")); + if (buffer.Length - offset < count) + throw new ArgumentException(Environment.GetResourceString("Argument_InvalidOffLen")); + Contract.EndContractBlock(); + + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Write() or BeginWrite() which a subclass might have overriden. + // To be safe we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Write/BeginWrite) when we are not sure. + if (this.GetType() != typeof(FileStream)) + return base.WriteAsync(buffer, offset, count, cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (_handle.IsClosed) + __Error.FileNotOpen(); + + // If async IO is not supported on this platform or + // if this FileStream was not opened with FileOptions.Asynchronous. + if (!_isAsync) + return base.WriteAsync(buffer, offset, count, cancellationToken); + + var writeTask = new FileStreamReadWriteTask<VoidTaskResult>(cancellationToken); + var endWriteTask = s_endWriteTask; + if (endWriteTask == null) s_endWriteTask = endWriteTask = EndWriteTask; // benign initialization race condition + writeTask._asyncResult = BeginWriteAsync(buffer, offset, count, endWriteTask, writeTask); + + if (writeTask._asyncResult.IsAsync && cancellationToken.CanBeCanceled) + { + var cancelWriteHandler = s_cancelWriteHandler; + if (cancelWriteHandler == null) s_cancelWriteHandler = cancelWriteHandler = CancelTask<VoidTaskResult>; // benign initialization race condition + writeTask._registration = cancellationToken.Register(cancelWriteHandler, writeTask); + + // In case the task is completed right before we register the cancellation callback. + if (writeTask._asyncResult.IsCompleted) + writeTask._registration.Dispose(); + } + + return writeTask; + } + + // The task instance returned from ReadAsync and WriteAsync. + // Also stores all of the state necessary for those calls to avoid closures and extraneous delegate allocations. + private sealed class FileStreamReadWriteTask<T> : Task<T> + { + internal CancellationToken _cancellationToken; + internal CancellationTokenRegistration _registration; + internal FileStreamAsyncResult _asyncResult; // initialized after Begin call completes + + internal FileStreamReadWriteTask(CancellationToken cancellationToken) : base() + { + _cancellationToken = cancellationToken; + } + } + + // Cancellation callback for both ReadAsync and WriteAsync. + [SecuritySafeCritical] + private static void CancelTask<T>(object state) + { + var task = state as FileStreamReadWriteTask<T>; + Contract.Assert(task != null); + FileStreamAsyncResult asyncResult = task._asyncResult; + + // This method is used as both the completion callback and the cancellation callback. + // We should try to cancel the operation if this is running as the completion callback + // or if cancellation is not applicable: + // 1. asyncResult is not a FileStreamAsyncResult + // 2. asyncResult.IsAsync is false: asyncResult is a "synchronous" FileStreamAsyncResult. + // 3. The asyncResult is completed: this should never happen. + Contract.Assert((!asyncResult.IsWrite && typeof(T) == typeof(int)) || + (asyncResult.IsWrite && typeof(T) == typeof(VoidTaskResult))); + Contract.Assert(asyncResult != null); + Contract.Assert(asyncResult.IsAsync); + + try + { + // Cancel the overlapped read and set the task to cancelled state. + if (!asyncResult.IsCompleted) + asyncResult.Cancel(); + } + catch (Exception ex) + { + task.TrySetException(ex); + } + } + + // Completion callback for ReadAsync + [SecuritySafeCritical] + private static void EndReadTask(IAsyncResult iar) + { + FileStreamAsyncResult asyncResult = iar as FileStreamAsyncResult; + Contract.Assert(asyncResult != null); + Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?"); + + var readTask = asyncResult.AsyncState as FileStreamReadWriteTask<int>; + Contract.Assert(readTask != null); + + try + { + if (asyncResult.IsAsync) + { + asyncResult.ReleaseNativeResource(); + + // release the resource held by CancellationTokenRegistration + readTask._registration.Dispose(); + } + + if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED) + { + var cancellationToken = readTask._cancellationToken; + Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?"); + readTask.TrySetCanceled(cancellationToken); + } + else + readTask.TrySetResult(asyncResult.NumBytesRead); + } + catch (Exception ex) + { + readTask.TrySetException(ex); + } + } + + // Completion callback for WriteAsync + [SecuritySafeCritical] + private static void EndWriteTask(IAsyncResult iar) + { + var asyncResult = iar as FileStreamAsyncResult; + Contract.Assert(asyncResult != null); + Contract.Assert(asyncResult.IsCompleted, "How can we end up in the completion callback if the IAsyncResult is not completed?"); + + var writeTask = iar.AsyncState as FileStreamReadWriteTask<VoidTaskResult>; + Contract.Assert(writeTask != null); + + try + { + if (asyncResult.IsAsync) + { + asyncResult.ReleaseNativeResource(); + + // release the resource held by CancellationTokenRegistration + writeTask._registration.Dispose(); + } + + if (asyncResult.ErrorCode == Win32Native.ERROR_OPERATION_ABORTED) + { + var cancellationToken = writeTask._cancellationToken; + Contract.Assert(cancellationToken.IsCancellationRequested, "How can the IO operation be aborted if cancellation was not requested?"); + writeTask.TrySetCanceled(cancellationToken); + } + else + writeTask.TrySetResult(default(VoidTaskResult)); + } + catch (Exception ex) + { + writeTask.TrySetException(ex); + } + } + + // Unlike Flush(), FlushAsync() always flushes to disk. This is intentional. + // Legend is that we chose not to flush the OS file buffers in Flush() in fear of + // perf problems with frequent, long running FlushFileBuffers() calls. But we don't + // have that problem with FlushAsync() because we will call FlushFileBuffers() in the background. + [HostProtection(ExternalThreading = true)] + [ComVisible(false)] + [System.Security.SecuritySafeCritical] + public override Task FlushAsync(CancellationToken cancellationToken) + { + // If we have been inherited into a subclass, the following implementation could be incorrect + // since it does not call through to Flush() which a subclass might have overriden. To be safe + // we will only use this implementation in cases where we know it is safe to do so, + // and delegate to our base class (which will call into Flush) when we are not sure. + if (this.GetType() != typeof(FileStream)) + return base.FlushAsync(cancellationToken); + + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + if (_handle.IsClosed) + __Error.FileNotOpen(); + + // The always synchronous data transfer between the OS and the internal buffer is intentional + // because this is needed to allow concurrent async IO requests. Concurrent data transfer + // between the OS and the internal buffer will result in race conditions. Since FlushWrite and + // FlushRead modify internal state of the stream and transfer data between the OS and the + // internal buffer, they cannot be truly async. We will, however, flush the OS file buffers + // asynchronously because it doesn't modify any internal state of the stream and is potentially + // a long running process. + try + { + FlushInternalBuffer(); + } + catch (Exception e) + { + return Task.FromException(e); + } + + if (CanWrite) + return Task.Factory.StartNew( + state => ((FileStream)state).FlushOSBuffer(), + this, + cancellationToken, + TaskCreationOptions.DenyChildAttach, + TaskScheduler.Default); + else + return Task.CompletedTask; + } + + } +} |