// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Win32.SafeHandles;
using System.Diagnostics;
namespace System.IO
{
public partial class FileStream : Stream
{
private const FileShare DefaultShare = FileShare.Read;
private const bool DefaultIsAsync = false;
internal const int DefaultBufferSize = 4096;
private byte[] _buffer;
private int _bufferLength;
private readonly SafeFileHandle _fileHandle;
/// Whether the file is opened for reading, writing, or both.
private readonly FileAccess _access;
/// The path to the opened file.
private readonly string _path;
/// The next available byte to be read from the _buffer.
private int _readPos;
/// The number of valid bytes in _buffer.
private int _readLength;
/// The next location in which a write should occur to the buffer.
private int _writePos;
///
/// Whether asynchronous read/write/flush operations should be performed using async I/O.
/// On Windows FileOptions.Asynchronous controls how the file handle is configured,
/// and then as a result how operations are issued against that file handle. On Unix,
/// there isn't any distinction around how file descriptors are created for async vs
/// sync, but we still differentiate how the operations are issued in order to provide
/// similar behavioral semantics and performance characteristics as on Windows. On
/// Windows, if non-async, async read/write requests just delegate to the base stream,
/// and no attempt is made to synchronize between sync and async operations on the stream;
/// if async, then async read/write requests are implemented specially, and sync read/write
/// requests are coordinated with async ones by implementing the sync ones over the async
/// ones. On Unix, we do something similar. If non-async, async read/write requests just
/// delegate to the base stream, and no attempt is made to synchronize. If async, we use
/// a semaphore to coordinate both sync and async operations.
///
private readonly bool _useAsyncIO;
/// cached task for read ops that complete synchronously
private Task _lastSynchronouslyCompletedTask = null;
///
/// Currently cached position in the stream. This should always mirror the underlying file's actual position,
/// and should only ever be out of sync if another stream with access to this same file manipulates it, at which
/// point we attempt to error out.
///
private long _filePosition;
/// Whether the file stream's handle has been exposed.
private bool _exposedHandle;
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access)
: this(handle, access, true, DefaultBufferSize, false)
{
}
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle)
: this(handle, access, ownsHandle, DefaultBufferSize, false)
{
}
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize)
: this(handle, access, ownsHandle, bufferSize, false)
{
}
[Obsolete("This constructor has been deprecated. Please use new FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) instead, and optionally make a new SafeFileHandle with ownsHandle=false if needed. https://go.microsoft.com/fwlink/?linkid=14202")]
public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferSize, bool isAsync)
{
SafeFileHandle safeHandle = new SafeFileHandle(handle, ownsHandle: ownsHandle);
try
{
ValidateAndInitFromHandle(safeHandle, access, bufferSize, isAsync);
}
catch
{
// We don't want to take ownership of closing passed in handles
// *unless* the constructor completes successfully.
GC.SuppressFinalize(safeHandle);
// This would also prevent Close from being called, but is unnecessary
// as we've removed the object from the finalizer queue.
//
// safeHandle.SetHandleAsInvalid();
throw;
}
// Note: Cleaner to set the following fields in ValidateAndInitFromHandle,
// but we can't as they're readonly.
_access = access;
_useAsyncIO = isAsync;
// As the handle was passed in, we must set the handle field at the very end to
// avoid the finalizer closing the handle when we throw errors.
_fileHandle = safeHandle;
}
public FileStream(SafeFileHandle handle, FileAccess access)
: this(handle, access, DefaultBufferSize)
{
}
public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize)
: this(handle, access, bufferSize, GetDefaultIsAsync(handle))
{
}
private void ValidateAndInitFromHandle(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
{
if (handle.IsInvalid)
throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle));
if (access < FileAccess.Read || access > FileAccess.ReadWrite)
throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum);
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
if (handle.IsClosed)
throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.GetValueOrDefault())
throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle));
_exposedHandle = true;
_bufferLength = bufferSize;
InitFromHandle(handle, access, isAsync);
}
public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync)
{
ValidateAndInitFromHandle(handle, access, bufferSize, isAsync);
// Note: Cleaner to set the following fields in ValidateAndInitFromHandle,
// but we can't as they're readonly.
_access = access;
_useAsyncIO = isAsync;
// As the handle was passed in, we must set the handle field at the very end to
// avoid the finalizer closing the handle when we throw errors.
_fileHandle = handle;
}
public FileStream(string path, FileMode mode) :
this(path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), DefaultShare, DefaultBufferSize, DefaultIsAsync)
{ }
public FileStream(string path, FileMode mode, FileAccess access) :
this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync)
{ }
public FileStream(string path, FileMode mode, FileAccess access, FileShare share) :
this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync)
{ }
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) :
this(path, mode, access, share, bufferSize, DefaultIsAsync)
{ }
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) :
this(path, mode, access, share, bufferSize, useAsync ? FileOptions.Asynchronous : FileOptions.None)
{ }
public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options)
{
if (path == null)
throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path);
if (path.Length == 0)
throw new ArgumentException(SR.Argument_EmptyPath, nameof(path));
// don't include inheritable in our bounds check for share
FileShare tempshare = share & ~FileShare.Inheritable;
string badArg = null;
if (mode < FileMode.CreateNew || mode > FileMode.Append)
badArg = nameof(mode);
else if (access < FileAccess.Read || access > FileAccess.ReadWrite)
badArg = nameof(access);
else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete))
badArg = nameof(share);
if (badArg != null)
throw new ArgumentOutOfRangeException(badArg, SR.ArgumentOutOfRange_Enum);
// NOTE: any change to FileOptions enum needs to be matched here in the error validation
if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0)
throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum);
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum);
// Write access validation
if ((access & FileAccess.Write) == 0)
{
if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
{
// No write access, mode and access disagree but flag access since mode comes first
throw new ArgumentException(SR.Format(SR.Argument_InvalidFileModeAndAccessCombo, mode, access), nameof(access));
}
}
if ((access & FileAccess.Read) != 0 && mode == FileMode.Append)
throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access));
string fullPath = Path.GetFullPath(path);
_path = fullPath;
_access = access;
_bufferLength = bufferSize;
if ((options & FileOptions.Asynchronous) != 0)
_useAsyncIO = true;
_fileHandle = OpenHandle(mode, share, options);
try
{
Init(mode, share, path);
}
catch
{
// If anything goes wrong while setting up the stream, make sure we deterministically dispose
// of the opened handle.
_fileHandle.Dispose();
_fileHandle = null;
throw;
}
}
[Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. https://go.microsoft.com/fwlink/?linkid=14202")]
public virtual IntPtr Handle { get { return SafeFileHandle.DangerousGetHandle(); } }
public virtual void Lock(long position, long length)
{
if (position < 0 || length < 0)
{
throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
}
if (_fileHandle.IsClosed)
{
throw Error.GetFileNotOpen();
}
LockInternal(position, length);
}
public virtual void Unlock(long position, long length)
{
if (position < 0 || length < 0)
{
throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum);
}
if (_fileHandle.IsClosed)
{
throw Error.GetFileNotOpen();
}
UnlockInternal(position, length);
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
// If we have been inherited into a subclass, the following implementation could be incorrect
// since it does not call through to Flush() which a subclass might have overridden. To be safe
// we will only use this implementation in cases where we know it is safe to do so,
// and delegate to our base class (which will call into Flush) when we are not sure.
if (GetType() != typeof(FileStream))
return base.FlushAsync(cancellationToken);
return FlushAsyncInternal(cancellationToken);
}
public override int Read(byte[] array, int offset, int count)
{
ValidateReadWriteArgs(array, offset, count);
return _useAsyncIO ?
ReadAsyncTask(array, offset, count, CancellationToken.None).GetAwaiter().GetResult() :
ReadSpan(new Span(array, offset, count));
}
public override int Read(Span buffer)
{
if (GetType() == typeof(FileStream) && !_useAsyncIO)
{
if (_fileHandle.IsClosed)
{
throw Error.GetFileNotOpen();
}
return ReadSpan(buffer);
}
else
{
// This type is derived from FileStream and/or the stream is in async mode. If this is a
// derived type, it may have overridden Read(byte[], int, int) prior to this Read(Span)
// overload being introduced. In that case, this Read(Span) overload should use the behavior
// of Read(byte[],int,int) overload. Or if the stream is in async mode, we can't call the
// synchronous ReadSpan, so we similarly call the base Read, which will turn delegate to
// Read(byte[],int,int), which will do the right thing if we're in async mode.
return base.Read(buffer);
}
}
public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
if (buffer.Length - offset < count)
throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
// If we have been inherited into a subclass, the following implementation could be incorrect
// since it does not call through to Read() which a subclass might have overridden.
// To be safe we will only use this implementation in cases where we know it is safe to do so,
// and delegate to our base class (which will call into Read/ReadAsync) when we are not sure.
// Similarly, if we weren't opened for asynchronous I/O, call to the base implementation so that
// Read is invoked asynchronously.
if (GetType() != typeof(FileStream) || !_useAsyncIO)
return base.ReadAsync(buffer, offset, count, cancellationToken);
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
if (IsClosed)
throw Error.GetFileNotOpen();
return ReadAsyncTask(buffer, offset, count, cancellationToken);
}
public override ValueTask ReadAsync(Memory buffer, CancellationToken cancellationToken = default)
{
if (!_useAsyncIO || GetType() != typeof(FileStream))
{
// If we're not using async I/O, delegate to the base, which will queue a call to Read.
// Or if this isn't a concrete FileStream, a derived type may have overridden ReadAsync(byte[],...),
// which was introduced first, so delegate to the base which will delegate to that.
return base.ReadAsync(buffer, cancellationToken);
}
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask(Task.FromCanceled(cancellationToken));
}
if (IsClosed)
{
throw Error.GetFileNotOpen();
}
Task t = ReadAsyncInternal(buffer, cancellationToken, out int synchronousResult);
return t != null ?
new ValueTask(t) :
new ValueTask(synchronousResult);
}
private Task ReadAsyncTask(byte[] array, int offset, int count, CancellationToken cancellationToken)
{
Task t = ReadAsyncInternal(new Memory(array, offset, count), cancellationToken, out int synchronousResult);
if (t == null)
{
t = _lastSynchronouslyCompletedTask;
Debug.Assert(t == null || t.IsCompletedSuccessfully, "Cached task should have completed successfully");
if (t == null || t.Result != synchronousResult)
{
_lastSynchronouslyCompletedTask = t = Task.FromResult(synchronousResult);
}
}
return t;
}
public override void Write(byte[] array, int offset, int count)
{
ValidateReadWriteArgs(array, offset, count);
if (_useAsyncIO)
{
WriteAsyncInternal(new ReadOnlyMemory(array, offset, count), CancellationToken.None).GetAwaiter().GetResult();
}
else
{
WriteSpan(new ReadOnlySpan(array, offset, count));
}
}
public override void Write(ReadOnlySpan buffer)
{
if (GetType() == typeof(FileStream) && !_useAsyncIO)
{
if (_fileHandle.IsClosed)
{
throw Error.GetFileNotOpen();
}
WriteSpan(buffer);
}
else
{
// This type is derived from FileStream and/or the stream is in async mode. If this is a
// derived type, it may have overridden Write(byte[], int, int) prior to this Write(ReadOnlySpan)
// overload being introduced. In that case, this Write(ReadOnlySpan) overload should use the behavior
// of Write(byte[],int,int) overload. Or if the stream is in async mode, we can't call the
// synchronous WriteSpan, so we similarly call the base Write, which will turn delegate to
// Write(byte[],int,int), which will do the right thing if we're in async mode.
base.Write(buffer);
}
}
public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (buffer == null)
throw new ArgumentNullException(nameof(buffer), SR.ArgumentNull_Buffer);
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
if (buffer.Length - offset < count)
throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
// If we have been inherited into a subclass, the following implementation could be incorrect
// since it does not call through to Write() or WriteAsync() which a subclass might have overridden.
// To be safe we will only use this implementation in cases where we know it is safe to do so,
// and delegate to our base class (which will call into Write/WriteAsync) when we are not sure.
if (!_useAsyncIO || GetType() != typeof(FileStream))
return base.WriteAsync(buffer, offset, count, cancellationToken);
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
if (IsClosed)
throw Error.GetFileNotOpen();
return WriteAsyncInternal(new ReadOnlyMemory(buffer, offset, count), cancellationToken).AsTask();
}
public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken = default)
{
if (!_useAsyncIO || GetType() != typeof(FileStream))
{
// If we're not using async I/O, delegate to the base, which will queue a call to Write.
// Or if this isn't a concrete FileStream, a derived type may have overridden WriteAsync(byte[],...),
// which was introduced first, so delegate to the base which will delegate to that.
return base.WriteAsync(buffer, cancellationToken);
}
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask(Task.FromCanceled(cancellationToken));
}
if (IsClosed)
{
throw Error.GetFileNotOpen();
}
return WriteAsyncInternal(buffer, cancellationToken);
}
///
/// Clears buffers for this stream and causes any buffered data to be written to the file.
///
public override void Flush()
{
// Make sure that we call through the public virtual API
Flush(flushToDisk: false);
}
///
/// Clears buffers for this stream, and if is true,
/// causes any buffered data to be written to the file.
///
public virtual void Flush(bool flushToDisk)
{
if (IsClosed) throw Error.GetFileNotOpen();
FlushInternalBuffer();
if (flushToDisk && CanWrite)
{
FlushOSBuffer();
}
}
/// Gets a value indicating whether the current stream supports reading.
public override bool CanRead
{
get { return !_fileHandle.IsClosed && (_access & FileAccess.Read) != 0; }
}
/// Gets a value indicating whether the current stream supports writing.
public override bool CanWrite
{
get { return !_fileHandle.IsClosed && (_access & FileAccess.Write) != 0; }
}
/// Validates arguments to Read and Write and throws resulting exceptions.
/// The buffer to read from or write to.
/// The zero-based offset into the array.
/// The maximum number of bytes to read or write.
private void ValidateReadWriteArgs(byte[] array, int offset, int count)
{
if (array == null)
throw new ArgumentNullException(nameof(array), SR.ArgumentNull_Buffer);
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
if (count < 0)
throw new ArgumentOutOfRangeException(nameof(count), SR.ArgumentOutOfRange_NeedNonNegNum);
if (array.Length - offset < count)
throw new ArgumentException(SR.Argument_InvalidOffLen /*, no good single parameter name to pass*/);
if (_fileHandle.IsClosed)
throw Error.GetFileNotOpen();
}
/// Sets the length of this stream to the given value.
/// The new length of the stream.
public override void SetLength(long value)
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
if (_fileHandle.IsClosed)
throw Error.GetFileNotOpen();
if (!CanSeek)
throw Error.GetSeekNotSupported();
if (!CanWrite)
throw Error.GetWriteNotSupported();
SetLengthInternal(value);
}
public virtual SafeFileHandle SafeFileHandle
{
get
{
Flush();
_exposedHandle = true;
return _fileHandle;
}
}
/// Gets the path that was passed to the constructor.
public virtual string Name { get { return _path ?? SR.IO_UnknownFileName; } }
/// Gets a value indicating whether the stream was opened for I/O to be performed synchronously or asynchronously.
public virtual bool IsAsync
{
get { return _useAsyncIO; }
}
/// Gets the length of the stream in bytes.
public override long Length
{
get
{
if (_fileHandle.IsClosed) throw Error.GetFileNotOpen();
if (!CanSeek) throw Error.GetSeekNotSupported();
return GetLengthInternal();
}
}
///
/// Verify that the actual position of the OS's handle equals what we expect it to.
/// This will fail if someone else moved the UnixFileStream's handle or if
/// our position updating code is incorrect.
///
private void VerifyOSHandlePosition()
{
bool verifyPosition = _exposedHandle; // in release, only verify if we've given out the handle such that someone else could be manipulating it
#if DEBUG
verifyPosition = true; // in debug, always make sure our position matches what the OS says it should be
#endif
if (verifyPosition && CanSeek)
{
long oldPos = _filePosition; // SeekCore will override the current _position, so save it now
long curPos = SeekCore(_fileHandle, 0, SeekOrigin.Current);
if (oldPos != curPos)
{
// For reads, this is non-fatal but we still could have returned corrupted
// data in some cases, so discard the internal buffer. For writes,
// this is a problem; discard the buffer and error out.
_readPos = _readLength = 0;
if (_writePos > 0)
{
_writePos = 0;
throw new IOException(SR.IO_FileStreamHandlePosition);
}
}
}
}
/// Verifies that state relating to the read/write buffer is consistent.
[Conditional("DEBUG")]
private void AssertBufferInvariants()
{
// Read buffer values must be in range: 0 <= _bufferReadPos <= _bufferReadLength <= _bufferLength
Debug.Assert(0 <= _readPos && _readPos <= _readLength && _readLength <= _bufferLength);
// Write buffer values must be in range: 0 <= _bufferWritePos <= _bufferLength
Debug.Assert(0 <= _writePos && _writePos <= _bufferLength);
// Read buffering and write buffering can't both be active
Debug.Assert((_readPos == 0 && _readLength == 0) || _writePos == 0);
}
/// Validates that we're ready to read from the stream.
private void PrepareForReading()
{
if (_fileHandle.IsClosed)
throw Error.GetFileNotOpen();
if (_readLength == 0 && !CanRead)
throw Error.GetReadNotSupported();
AssertBufferInvariants();
}
/// Gets or sets the position within the current stream
public override long Position
{
get
{
if (_fileHandle.IsClosed)
throw Error.GetFileNotOpen();
if (!CanSeek)
throw Error.GetSeekNotSupported();
AssertBufferInvariants();
VerifyOSHandlePosition();
// We may have read data into our buffer from the handle, such that the handle position
// is artificially further along than the consumer's view of the stream's position.
// Thus, when reading, our position is really starting from the handle position negatively
// offset by the number of bytes in the buffer and positively offset by the number of
// bytes into that buffer we've read. When writing, both the read length and position
// must be zero, and our position is just the handle position offset positive by how many
// bytes we've written into the buffer.
return (_filePosition - _readLength) + _readPos + _writePos;
}
set
{
if (value < 0)
throw new ArgumentOutOfRangeException(nameof(value), SR.ArgumentOutOfRange_NeedNonNegNum);
Seek(value, SeekOrigin.Begin);
}
}
internal virtual bool IsClosed => _fileHandle.IsClosed;
private static bool IsIoRelatedException(Exception e) =>
// These all derive from IOException
// DirectoryNotFoundException
// DriveNotFoundException
// EndOfStreamException
// FileLoadException
// FileNotFoundException
// PathTooLongException
// PipeException
e is IOException ||
// Note that SecurityException is only thrown on runtimes that support CAS
// e is SecurityException ||
e is UnauthorizedAccessException ||
e is NotSupportedException ||
(e is ArgumentException && !(e is ArgumentNullException));
///
/// Gets the array used for buffering reading and writing.
/// If the array hasn't been allocated, this will lazily allocate it.
///
/// The buffer.
private byte[] GetBuffer()
{
Debug.Assert(_buffer == null || _buffer.Length == _bufferLength);
if (_buffer == null)
{
_buffer = new byte[_bufferLength];
OnBufferAllocated();
}
return _buffer;
}
partial void OnBufferAllocated();
///
/// Flushes the internal read/write buffer for this stream. If write data has been buffered,
/// that data is written out to the underlying file. Or if data has been buffered for
/// reading from the stream, the data is dumped and our position in the underlying file
/// is rewound as necessary. This does not flush the OS buffer.
///
private void FlushInternalBuffer()
{
AssertBufferInvariants();
if (_writePos > 0)
{
FlushWriteBuffer();
}
else if (_readPos < _readLength && CanSeek)
{
FlushReadBuffer();
}
}
/// Dumps any read data in the buffer and rewinds our position in the stream, accordingly, as necessary.
private void FlushReadBuffer()
{
// Reading is done by blocks from the file, but someone could read
// 1 byte from the buffer then write. At that point, the OS's file
// pointer is out of sync with the stream's position. All write
// functions should call this function to preserve the position in the file.
AssertBufferInvariants();
Debug.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushReadBuffer!");
int rewind = _readPos - _readLength;
if (rewind != 0)
{
Debug.Assert(CanSeek, "FileStream will lose buffered read data now.");
SeekCore(_fileHandle, rewind, SeekOrigin.Current);
}
_readPos = _readLength = 0;
}
///
/// Reads a byte from the file stream. Returns the byte cast to an int
/// or -1 if reading from the end of the stream.
///
public override int ReadByte()
{
PrepareForReading();
byte[] buffer = GetBuffer();
if (_readPos == _readLength)
{
FlushWriteBuffer();
_readLength = FillReadBufferForReadByte();
_readPos = 0;
if (_readLength == 0)
{
return -1;
}
}
return buffer[_readPos++];
}
///
/// Writes a byte to the current position in the stream and advances the position
/// within the stream by one byte.
///
/// The byte to write to the stream.
public override void WriteByte(byte value)
{
PrepareForWriting();
// Flush the write buffer if it's full
if (_writePos == _bufferLength)
FlushWriteBufferForWriteByte();
// We now have space in the buffer. Store the byte.
GetBuffer()[_writePos++] = value;
}
///
/// Validates that we're ready to write to the stream,
/// including flushing a read buffer if necessary.
///
private void PrepareForWriting()
{
if (_fileHandle.IsClosed)
throw Error.GetFileNotOpen();
// Make sure we're good to write. We only need to do this if there's nothing already
// in our write buffer, since if there is something in the buffer, we've already done
// this checking and flushing.
if (_writePos == 0)
{
if (!CanWrite) throw Error.GetWriteNotSupported();
FlushReadBuffer();
Debug.Assert(_bufferLength > 0, "_bufferSize > 0");
}
}
~FileStream()
{
// Preserved for compatibility since FileStream has defined a
// finalizer in past releases and derived classes may depend
// on Dispose(false) call.
Dispose(false);
}
public override IAsyncResult BeginRead(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
if (numBytes < 0)
throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
if (array.Length - offset < numBytes)
throw new ArgumentException(SR.Argument_InvalidOffLen);
if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
if (!CanRead) throw new NotSupportedException(SR.NotSupported_UnreadableStream);
if (!IsAsync)
return base.BeginRead(array, offset, numBytes, callback, state);
else
return TaskToApm.Begin(ReadAsyncTask(array, offset, numBytes, CancellationToken.None), callback, state);
}
public override IAsyncResult BeginWrite(byte[] array, int offset, int numBytes, AsyncCallback callback, object state)
{
if (array == null)
throw new ArgumentNullException(nameof(array));
if (offset < 0)
throw new ArgumentOutOfRangeException(nameof(offset), SR.ArgumentOutOfRange_NeedNonNegNum);
if (numBytes < 0)
throw new ArgumentOutOfRangeException(nameof(numBytes), SR.ArgumentOutOfRange_NeedNonNegNum);
if (array.Length - offset < numBytes)
throw new ArgumentException(SR.Argument_InvalidOffLen);
if (IsClosed) throw new ObjectDisposedException(SR.ObjectDisposed_FileClosed);
if (!CanWrite) throw new NotSupportedException(SR.NotSupported_UnwritableStream);
if (!IsAsync)
return base.BeginWrite(array, offset, numBytes, callback, state);
else
return TaskToApm.Begin(WriteAsyncInternal(new ReadOnlyMemory(array, offset, numBytes), CancellationToken.None).AsTask(), callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
if (!IsAsync)
return base.EndRead(asyncResult);
else
return TaskToApm.End(asyncResult);
}
public override void EndWrite(IAsyncResult asyncResult)
{
if (asyncResult == null)
throw new ArgumentNullException(nameof(asyncResult));
if (!IsAsync)
base.EndWrite(asyncResult);
else
TaskToApm.End(asyncResult);
}
}
}