singrdk/base/Libraries/System.IO/FileStream.cs

781 lines
34 KiB
C#

// ==++==
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// ==--==
//============================================================
//
// Class: FileStream
//
// Purpose: Exposes a Stream around a file, with full
// synchronous and asynchronous support, and buffering.
//
//===========================================================
#define BLACKHOLE
using System;
using System.Diagnostics;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
using Microsoft.SingSharp;
using Microsoft.Singularity;
using Microsoft.Singularity.Directory;
using Microsoft.Singularity.Channels;
using Microsoft.Singularity.FileSystem;
using Microsoft.Singularity.V1.Services;
using FileSystem.Utils;
//
// Implementation notes:
//
// I've added buffering into FileStream as well. I 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.
//
// 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
{
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream"]/*' />
public class FileStream : Stream
{
internal const int DefaultBufferSize = 4096;
private TRef<FileContract.Imp:Ready> _imp;
private byte[] _buffer; // Shared read/write buffer. Alloc on first use.
private String _fileName; // Fully qualified file name.
private bool _canRead;
private bool _canWrite;
private bool _canSeek;
private bool _disposed;
#if BLACKHOLE
private bool _isBlackHole;// Absorbs writes and gives nothing back.
#endif
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.
private long _pos; // Cache current location in the file.
private long _len;
private IntPtr _handle;
private long _appendStart;// When appending, prevent overwriting file.
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.FileStream"]/*' />
[Microsoft.Contracts.NotDelayed]
public FileStream(DirectoryServiceContract.Imp:Ready! dsRoot, String path, FileMode mode)
: this(dsRoot, path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, Path.GetFileName(path), false) {
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.FileStream"]/*' />
[Microsoft.Contracts.NotDelayed]
public FileStream(String path, FileMode mode)
{
DirectoryServiceContract.Imp dsRoot = DirectoryService.NewClientEndpoint();
this(dsRoot, path, mode, (mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite), FileShare.Read, DefaultBufferSize, Path.GetFileName(path), false);
delete dsRoot;
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.FileStream1"]/*' />
[Microsoft.Contracts.NotDelayed]
public FileStream(DirectoryServiceContract.Imp:Ready! dsRoot, String path, FileMode mode, FileAccess access)
: this(dsRoot, path, mode, access, FileShare.Read, DefaultBufferSize, Path.GetFileName(path), false) {
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.FileStream1"]/*' />
[Microsoft.Contracts.NotDelayed]
public FileStream(String path, FileMode mode, FileAccess access)
{
DirectoryServiceContract.Imp dsRoot = DirectoryService.NewClientEndpoint();
this(dsRoot, path, mode, access, FileShare.Read, DefaultBufferSize, Path.GetFileName(path), false);
delete dsRoot;
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.FileStream2"]/*' />
[Microsoft.Contracts.NotDelayed]
public FileStream(String path, FileMode mode, FileAccess access, FileShare share)
{
DirectoryServiceContract.Imp dsRoot = DirectoryService.NewClientEndpoint();
this(dsRoot, path, mode, access, share, DefaultBufferSize, Path.GetFileName(path), false);
delete dsRoot;
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.FileStream3"]/*' />
[Microsoft.Contracts.NotDelayed]
public FileStream(String path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
{
DirectoryServiceContract.Imp dsRoot = DirectoryService.NewClientEndpoint();
this(dsRoot, path, mode, access, share, bufferSize, Path.GetFileName(path), false);
delete dsRoot;
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.FileStream3"]/*' />
[Microsoft.Contracts.NotDelayed]
public FileStream(DirectoryServiceContract.Imp:Ready! dsRoot, String path, FileMode mode, FileAccess access, FileShare share, int bufferSize)
: this(dsRoot, path, mode, access, share, bufferSize, Path.GetFileName(path), false) {
}
[Microsoft.Contracts.NotDelayed]
internal FileStream(DirectoryServiceContract.Imp:Ready! dsRoot, String path, FileMode mode, FileAccess access, FileShare share, int bufferSize, String msgPath, bool bFromProxy)
{
// Note: msgPath must be safe to hand back to untrusted code.
_fileName = msgPath; // To handle odd cases of finalizing partially constructed objects.
if (path == null)
throw new ArgumentNullException("path", "ArgumentNull_Path");
if (path.Length == 0)
throw new ArgumentException("Argument_EmptyPath");
if (mode < FileMode.CreateNew || mode > FileMode.Append ||
access < FileAccess.Read || access > FileAccess.ReadWrite ||
share < FileShare.None || share > FileShare.ReadWrite) {
String badArg = "mode";
if (access < FileAccess.Read || access > FileAccess.ReadWrite)
badArg = "access";
if (share < FileShare.None || share > FileShare.ReadWrite)
badArg = "share";
throw new ArgumentOutOfRangeException(badArg, "ArgumentOutOfRange_Enum");
}
if (bufferSize <= 0)
throw new ArgumentOutOfRangeException("bufferSize", "ArgumentOutOfRange_NeedPosNum");
path = Path.GetFullPath(path);
#if false // uncomment to debug file access.
Console.WriteLine("FileStream({0})", path);
#endif
_fileName = path;
FileAttributesRecord fileAttributes;
ErrorCode error;
FileUtils.GetAttributes(path, dsRoot, out fileAttributes, out error);
_len = fileAttributes.FileSize;
// 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.
if ((access & FileAccess.Read) != 0) {
if (mode == FileMode.Append)
throw new ArgumentException("Argument_InvalidAppendMode");
}
#if BLACKHOLE
if (error == ErrorCode.NotFound &&
(access & FileAccess.Write) != 0 &&
path.StartsWith("/blackhole/")) {
// We only hand the case where attempting to write to
// a non-existent file, in which we create a block hole for
// Bartok.
_canRead = (access & FileAccess.Read) != 0;
_canWrite = (access & FileAccess.Write) != 0;
_canSeek = true;
_pos = 0;
_bufferSize = bufferSize;
_readPos = 0;
_readLen = 0;
_writePos = 0;
_appendStart = -1;
_isBlackHole = true;
return;
}
#endif
// 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.
if ((access & FileAccess.Write) != 0) {
}
else {
// No write access
if (mode == FileMode.Truncate || mode == FileMode.CreateNew || mode == FileMode.Create || mode == FileMode.Append)
throw new ArgumentException(String.Format("Argument_InvalidFileMode&AccessCombo", mode, access));
}
bool seekToEnd = (mode == FileMode.Append);
// Must use a valid Win32 constant here...
if (mode == FileMode.Append)
mode = FileMode.OpenOrCreate;
ErrorCode errorCode;
if (mode == FileMode.CreateNew || mode == FileMode.Create) {
if (File.Exists(path)) {
if (mode == FileMode.CreateNew) {
__Error.WinIOError(__Error.ERROR_FILE_EXISTS, path);
assume false;
}
File.Delete(path);
}
if (FileUtils.CreateFile(path, out errorCode) != 0) {
#if false
Console.WriteLine("FileUtils.CreateFile({0}) failed. Error={1}", path, SdsUtils.ErrorCodeToString(errorCode));
#endif
if (mode == FileMode.Create) {
__Error.WinIOError(-1, _fileName);
assume false; // never get here
}
}
}
FileContract.Imp fi = FileUtils.OpenFile(path, dsRoot, out error);
if (error != ErrorCode.NoError || fi == null) {
#if false
Console.WriteLine("FileUtils.OpenFile({0}, out {1})",
path, (int)error);
#endif
// Return a meaningful error, 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.
__Error.WinIOError((int)error, _fileName);
assume false; // never get here
}
_imp = new TRef<FileContract.Imp:Ready>(fi);
_canRead = (access & FileAccess.Read) != 0;
_canWrite = (access & FileAccess.Write) != 0;
_canSeek = true;
_pos = 0;
_bufferSize = bufferSize;
_readPos = 0;
_readLen = 0;
_writePos = 0;
// For Append mode...
if (seekToEnd) {
_appendStart = SeekCore(0, SeekOrigin.End);
}
else {
_appendStart = -1;
}
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.CanRead"]/*' />
public override bool CanRead {
get { return _canRead; }
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.CanWrite"]/*' />
public override bool CanWrite {
get { return _canWrite; }
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.CanSeek"]/*' />
public override bool CanSeek {
get { return _canSeek; }
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Length"]/*' />
public override long Length {
get {
if (!CanSeek) __Error.SeekNotSupported();
#if false
long len = 0;
#else
long len = Native.GetFileSize(_handle);
#endif
// If we're writing near the end of the file, we must include our
// internal buffer in our Length calculation.
if (_writePos > 0 && _pos + _writePos > len)
len = _writePos + _pos;
return len;
}
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Name"]/*' />
public String Name {
get {
if (_fileName == null)
return "IO_UnknownFileName";
return _fileName;
}
}
internal String NameInternal {
get {
if (_fileName == null)
return "<UnknownFileName>";
return _fileName;
}
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Position"]/*' />
public override long Position {
get {
if (!CanSeek) __Error.SeekNotSupported();
Debug.Assert((_readPos==0 && _readLen==0 && _writePos >= 0) || (_writePos==0 && _readPos <= _readLen), "We're either reading or writing, but not both.");
return _pos + (_readPos - _readLen + _writePos);
}
set {
if (value < 0) throw new ArgumentOutOfRangeException("value", "ArgumentOutOfRange_NeedNonNegNum");
if (_writePos > 0) FlushWrite();
_readPos = 0;
_readLen = 0;
Seek(value, SeekOrigin.Begin);
}
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Close"]/*' />
public override void Close()
{
Dispose(true);
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Dispose"]/*' />
protected virtual void Dispose(bool disposing)
{
// Nothing will be done differently based on whether we are
// disposing vs. finalizing.
#if false
if (_handle != IntPtr.Zero) {
Flush();
_handle = IntPtr.Zero;
}
#endif
if (_disposed == false && _isBlackHole == false) {
// NB black has no associated imp.
FileContract.Imp fi = _imp.Acquire();
delete fi;
_canRead = false;
_canWrite = false;
_canSeek = false;
_buffer = null;
_disposed = true;
}
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Finalize"]/*' />
~FileStream()
{
#if false
if (_handle != IntPtr.Zero) {
Dispose(false);
}
#endif
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Flush"]/*' />
public override void Flush()
{
if (_writePos > 0) {
FlushWrite();
}
else if (_readPos < _readLen && CanSeek) {
FlushRead();
}
}
// 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() {
Debug.Assert(_writePos == 0, "FileStream: Write buffer must be empty in FlushRead!");
if (_readPos - _readLen != 0)
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 dirty data (_writePos > 0), this function must be called.
private void FlushWrite() {
Debug.Assert(_readPos == 0 && _readLen == 0, "FileStream: Read buffer must be empty in FlushWrite!");
WriteCore(_buffer, 0, _writePos);
_writePos = 0;
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.SetLength"]/*' />
public override void SetLength(long value)
{
if (value < 0)
throw new ArgumentOutOfRangeException("value", "ArgumentOutOfRange_NeedNonNegNum");
if (!CanSeek) __Error.SeekNotSupported();
if (!CanWrite) __Error.WriteNotSupported();
// Handle buffering updates.
if (_writePos > 0) {
FlushWrite();
}
else if (_readPos < _readLen) {
FlushRead();
}
if (_appendStart != -1 && value < _appendStart)
throw new IOException("IO.IO_SetLengthAppendTruncate");
long origPos = _pos;
if (_pos != value)
SeekCore(value, SeekOrigin.Begin);
#if false
if (!Native.SetEndOfFile(_handle)) {
__Error.WinIOError(0, String.Empty);
}
#endif
// 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);
}
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Read"]/*' />
public override int Read([In, Out] byte[] array, int offset, int count) {
if (array == null)
throw new ArgumentNullException("array", "ArgumentNull_Buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", "ArgumentOutOfRange_NeedNonNegNum");
if (count < 0)
throw new ArgumentOutOfRangeException("count", "ArgumentOutOfRange_NeedNonNegNum");
if (array.Length - offset < count)
throw new ArgumentException("Argument_InvalidOffLen");
Debug.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();
if (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.BlockCopy(_buffer, _readPos, array, offset, n);
_readPos += n;
// 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) {
Debug.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;
}
private int ReadCore(byte[] buffer, int offset, int count) {
Debug.Assert(CanRead, "CanRead");
Debug.Assert(_writePos == 0, "_writePos == 0");
#if BLACKHOLE
if (_isBlackHole) {
return 0;
}
#endif
FileContract.Imp fi = _imp.Acquire();
#if false
Console.WriteLine("ReadCore(offset={0}, count={1}, _pos={2}/{3})",
offset, count, _pos, _len);
if (offset != 0) {
DebugStub.Break();
}
#endif
long r = FileUtils.Read(fi, offset, count, _pos, (!)buffer);
if (r == -1) {
__Error.WinIOError(1, String.Empty);
}
#if false // uncomment to debug file access.
Console.WriteLine(" {0:x2}{1:x2}{2:x2}{3:x2} {4:x2}{5:x2}{6:x2}{7:x2}" +
" {8:x2}{9:x2}{10:x2}{11:x2} {12:x2}{13:x2}{14:x2}{15:x2}",
(byte)(r > 0 ? buffer[0] : (byte)0),
(byte)(r > 1 ? buffer[1] : (byte)0),
(byte)(r > 2 ? buffer[2] : (byte)0),
(byte)(r > 3 ? buffer[3] : (byte)0),
(byte)(r > 4 ? buffer[4] : (byte)0),
(byte)(r > 5 ? buffer[5] : (byte)0),
(byte)(r > 6 ? buffer[6] : (byte)0),
(byte)(r > 7 ? buffer[7] : (byte)0),
(byte)(r > 8 ? buffer[8] : (byte)0),
(byte)(r > 9 ? buffer[9] : (byte)0),
(byte)(r > 10 ? buffer[10] : (byte)0),
(byte)(r > 11 ? buffer[11] : (byte)0),
(byte)(r > 12 ? buffer[12] : (byte)0),
(byte)(r > 13 ? buffer[13] : (byte)0),
(byte)(r > 14 ? buffer[14] : (byte)0),
(byte)(r > 15 ? buffer[15] : (byte)0));
#endif
_imp.Release(fi);
_pos += r;
return (int)r;
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Seek"]/*' />
public override long Seek(long offset, SeekOrigin origin) {
if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
throw new ArgumentException("Argument_InvalidSeekOrigin");
if (!CanSeek) __Error.SeekNotSupported();
Debug.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();
}
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);
}
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("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.BlockCopy(_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.BlockCopy(_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;
}
Debug.Assert(_readLen >= 0 && _readPos <= _readLen, "_readLen should be nonnegative, and _readPos should be less than or equal _readLen");
Debug.Assert(pos == Position, "Seek optimization: pos != Position! Buffer math was mangled.");
}
return pos;
}
// This doesn't do argument checking. Necessary for SetLength, which must
// set the file pointer beyond the end of the file.
private long SeekCore(long offset, SeekOrigin origin) {
Debug.Assert(origin>=SeekOrigin.Begin && origin<=SeekOrigin.End, "origin>=SeekOrigin.Begin && origin<=SeekOrigin.End");
#if false
int hr = 0;
long ret = 0;
ret = Native.SetFilePointer(_handle, offset, origin);
if (ret == -1) __Error.WinIOError(hr, String.Empty);
_pos = ret;
return ret;
#else
// mostly ignores origin.
if (origin == SeekOrigin.Begin) {
_pos = offset;
}
else if (origin == SeekOrigin.Current) {
_pos += offset;
}
else if (origin == SeekOrigin.End) {
_pos = _len + offset;
}
return _pos;
#endif
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Write"]/*' />
public override void Write(byte[] array, int offset, int count) {
if (array == null)
throw new ArgumentNullException("array", "ArgumentNull_Buffer");
if (offset < 0)
throw new ArgumentOutOfRangeException("offset", "ArgumentOutOfRange_NeedNonNegNum");
if (count < 0)
throw new ArgumentOutOfRangeException("count", "ArgumentOutOfRange_NeedNonNegNum");
if (array.Length - offset < count)
throw new ArgumentException("Argument_InvalidOffLen");
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.BlockCopy(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.
WriteCore(_buffer, 0, _writePos);
_writePos = 0;
}
// If the buffer would slow writes down, avoid buffer completely.
if (count >= _bufferSize) {
Debug.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.BlockCopy(array, offset, _buffer, _writePos, count);
_writePos = count;
return;
}
private void WriteCore(byte[]! buffer, int offset, int count) {
Debug.Assert(CanWrite, "CanWrite");
Debug.Assert(_readPos == _readLen, "_readPos == _readLen");
#if BLACKHOLE
if (_isBlackHole) {
_pos += count;
return;
}
#endif
FileContract.Imp fi = _imp.Acquire();
long r = FileUtils.Write(fi, offset, count, _pos, (!)buffer);
_imp.Release(fi);
if (r == -1) {
__Error.WinIOError(1, String.Empty);
}
_pos += r;
return;
}
// Reads a byte from the file stream. Returns the byte cast to an int
// or -1 if reading from the end of the stream.
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.ReadByte"]/*' />
public override int ReadByte() {
if (_readLen == 0 && !CanRead) __Error.ReadNotSupported();
Debug.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();
Debug.Assert(_bufferSize > 0, "_bufferSize > 0");
if (_buffer == null) _buffer = new byte[_bufferSize];
_readLen = ReadCore(_buffer, 0, _bufferSize);
_readPos = 0;
}
if (_readPos == _readLen)
return -1;
return _buffer[_readPos++];
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.WriteByte"]/*' />
public override void WriteByte(byte value)
{
if (_writePos == 0) {
if (!CanWrite) __Error.WriteNotSupported();
if (_readPos < _readLen) FlushRead();
_readPos = 0;
_readLen = 0;
Debug.Assert(_bufferSize > 0, "_bufferSize > 0");
if (_buffer == null) _buffer = new byte[_bufferSize];
}
if (_writePos == _bufferSize)
FlushWrite();
_buffer[_writePos++] = value;
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Lock"]/*' />
public virtual void Lock(long position, long length) {
if (position < 0 || length < 0)
throw new ArgumentOutOfRangeException((position < 0 ? "position" : "length"), "ArgumentOutOfRange_NeedNonNegNum");
#if false
if (!Native.LockFile(_handle, position, length))
__Error.WinIOError();
#endif
}
//| <include file='doc\FileStream.uex' path='docs/doc[@for="FileStream.Unlock"]/*' />
public virtual void Unlock(long position, long length) {
if (position < 0 || length < 0)
throw new ArgumentOutOfRangeException((position < 0 ? "position" : "length"), "ArgumentOutOfRange_NeedNonNegNum");
#if false
if (!Native.UnlockFile(_handle, position, length))
__Error.WinIOError();
#endif
}
}
}