// 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 Microsoft.Win32.SafeHandles;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace System.IO
{
/// Provides an implementation of a file stream for Unix files.
public partial class FileStream : Stream
{
/// File mode.
private FileMode _mode;
/// Advanced options requested when opening the file.
private FileOptions _options;
/// If the file was opened with FileMode.Append, the length of the file when opened; otherwise, -1.
private long _appendStart = -1;
///
/// Extra state used by the file stream when _useAsyncIO is true. This includes
/// the semaphore used to serialize all operation, the buffer/offset/count provided by the
/// caller for ReadAsync/WriteAsync operations, and the last successful task returned
/// synchronously from ReadAsync which can be reused if the count matches the next request.
/// Only initialized when is true.
///
private AsyncState _asyncState;
/// Lazily-initialized value for whether the file supports seeking.
private bool? _canSeek;
private SafeFileHandle OpenHandle(FileMode mode, FileShare share, FileOptions options)
{
// FileStream performs most of the general argument validation. We can assume here that the arguments
// are all checked and consistent (e.g. non-null-or-empty path; valid enums in mode, access, share, and options; etc.)
// Store the arguments
_mode = mode;
_options = options;
if (_useAsyncIO)
_asyncState = new AsyncState();
// Translate the arguments into arguments for an open call.
Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, _access, options); // FileShare currently ignored
// If the file gets created a new, we'll select the permissions for it. Most Unix utilities by default use 666 (read and
// write for all), so we do the same (even though this doesn't match Windows, where by default it's possible to write out
// a file and then execute it). No matter what we choose, it'll be subject to the umask applied by the system, such that the
// actual permissions will typically be less than what we select here.
const Interop.Sys.Permissions OpenPermissions =
Interop.Sys.Permissions.S_IRUSR | Interop.Sys.Permissions.S_IWUSR |
Interop.Sys.Permissions.S_IRGRP | Interop.Sys.Permissions.S_IWGRP |
Interop.Sys.Permissions.S_IROTH | Interop.Sys.Permissions.S_IWOTH;
// Open the file and store the safe handle.
return SafeFileHandle.Open(_path, openFlags, (int)OpenPermissions);
}
/// Initializes a stream for reading or writing a Unix file.
/// How the file should be opened.
/// What other access to the file should be allowed. This is currently ignored.
private void Init(FileMode mode, FileShare share)
{
_fileHandle.IsAsync = _useAsyncIO;
// Lock the file if requested via FileShare. This is only advisory locking. FileShare.None implies an exclusive
// lock on the file and all other modes use a shared lock. While this is not as granular as Windows, not mandatory,
// and not atomic with file opening, it's better than nothing.
Interop.Sys.LockOperations lockOperation = (share == FileShare.None) ? Interop.Sys.LockOperations.LOCK_EX : Interop.Sys.LockOperations.LOCK_SH;
if (Interop.Sys.FLock(_fileHandle, lockOperation | Interop.Sys.LockOperations.LOCK_NB) < 0)
{
// The only error we care about is EWOULDBLOCK, which indicates that the file is currently locked by someone
// else and we would block trying to access it. Other errors, such as ENOTSUP (locking isn't supported) or
// EACCES (the file system doesn't allow us to lock), will only hamper FileStream's usage without providing value,
// given again that this is only advisory / best-effort.
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (errorInfo.Error == Interop.Error.EWOULDBLOCK)
{
throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
}
}
// These provide hints around how the file will be accessed. Specifying both RandomAccess
// and Sequential together doesn't make sense as they are two competing options on the same spectrum,
// so if both are specified, we prefer RandomAccess (behavior on Windows is unspecified if both are provided).
Interop.Sys.FileAdvice fadv =
(_options & FileOptions.RandomAccess) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_RANDOM :
(_options & FileOptions.SequentialScan) != 0 ? Interop.Sys.FileAdvice.POSIX_FADV_SEQUENTIAL :
0;
if (fadv != 0)
{
CheckFileCall(Interop.Sys.PosixFAdvise(_fileHandle, 0, 0, fadv),
ignoreNotSupported: true); // just a hint.
}
// Jump to the end of the file if opened as Append.
if (_mode == FileMode.Append)
{
_appendStart = SeekCore(0, SeekOrigin.End);
}
}
/// Initializes a stream from an already open file handle (file descriptor).
/// The handle to the file.
/// The size of the buffer to use when buffering.
/// Whether access to the stream is performed asynchronously.
private void InitFromHandle(SafeFileHandle handle)
{
if (_useAsyncIO)
_asyncState = new AsyncState();
if (CanSeekCore) // use non-virtual CanSeekCore rather than CanSeek to avoid making virtual call during ctor
SeekCore(0, SeekOrigin.Current);
}
/// Translates the FileMode, FileAccess, and FileOptions values into flags to be passed when opening the file.
/// The FileMode provided to the stream's constructor.
/// The FileAccess provided to the stream's constructor
/// The FileOptions provided to the stream's constructor
/// The flags value to be passed to the open system call.
private static Interop.Sys.OpenFlags PreOpenConfigurationFromOptions(FileMode mode, FileAccess access, FileOptions options)
{
// Translate FileMode. Most of the values map cleanly to one or more options for open.
Interop.Sys.OpenFlags flags = default(Interop.Sys.OpenFlags);
switch (mode)
{
default:
case FileMode.Open: // Open maps to the default behavior for open(...). No flags needed.
break;
case FileMode.Append: // Append is the same as OpenOrCreate, except that we'll also separately jump to the end later
case FileMode.OpenOrCreate:
flags |= Interop.Sys.OpenFlags.O_CREAT;
break;
case FileMode.Create:
flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_TRUNC);
break;
case FileMode.CreateNew:
flags |= (Interop.Sys.OpenFlags.O_CREAT | Interop.Sys.OpenFlags.O_EXCL);
break;
case FileMode.Truncate:
flags |= Interop.Sys.OpenFlags.O_TRUNC;
break;
}
// Translate FileAccess. All possible values map cleanly to corresponding values for open.
switch (access)
{
case FileAccess.Read:
flags |= Interop.Sys.OpenFlags.O_RDONLY;
break;
case FileAccess.ReadWrite:
flags |= Interop.Sys.OpenFlags.O_RDWR;
break;
case FileAccess.Write:
flags |= Interop.Sys.OpenFlags.O_WRONLY;
break;
}
// Translate some FileOptions; some just aren't supported, and others will be handled after calling open.
// - Asynchronous: Handled in ctor, setting _useAsync and SafeFileHandle.IsAsync to true
// - DeleteOnClose: Doesn't have a Unix equivalent, but we approximate it in Dispose
// - Encrypted: No equivalent on Unix and is ignored
// - RandomAccess: Implemented after open if posix_fadvise is available
// - SequentialScan: Implemented after open if posix_fadvise is available
// - WriteThrough: Handled here
if ((options & FileOptions.WriteThrough) != 0)
{
flags |= Interop.Sys.OpenFlags.O_SYNC;
}
return flags;
}
/// Gets a value indicating whether the current stream supports seeking.
public override bool CanSeek => CanSeekCore;
/// Gets a value indicating whether the current stream supports seeking.
/// Separated out of CanSeek to enable making non-virtual call to this logic.
private bool CanSeekCore
{
get
{
if (_fileHandle.IsClosed)
{
return false;
}
if (!_canSeek.HasValue)
{
// Lazily-initialize whether we're able to seek, tested by seeking to our current location.
_canSeek = Interop.Sys.LSeek(_fileHandle, 0, Interop.Sys.SeekWhence.SEEK_CUR) >= 0;
}
return _canSeek.Value;
}
}
private long GetLengthInternal()
{
// Get the length of the file as reported by the OS
Interop.Sys.FileStatus status;
CheckFileCall(Interop.Sys.FStat(_fileHandle, out status));
long length = status.Size;
// But we may have buffered some data to be written that puts our length
// beyond what the OS is aware of. Update accordingly.
if (_writePos > 0 && _filePosition + _writePos > length)
{
length = _writePos + _filePosition;
}
return length;
}
/// Releases the unmanaged resources used by the stream.
/// true to release both managed and unmanaged resources; false to release only unmanaged resources.
protected override void Dispose(bool disposing)
{
try
{
if (_fileHandle != null && !_fileHandle.IsClosed)
{
// Flush any remaining data in the file
FlushWriteBuffer();
// If DeleteOnClose was requested when constructed, delete the file now.
// (Unix doesn't directly support DeleteOnClose, so we mimic it here.)
if (_path != null && (_options & FileOptions.DeleteOnClose) != 0)
{
// Since we still have the file open, this will end up deleting
// it (assuming we're the only link to it) once it's closed, but the
// name will be removed immediately.
Interop.Sys.Unlink(_path); // ignore errors; it's valid that the path may no longer exist
}
}
}
finally
{
if (_fileHandle != null && !_fileHandle.IsClosed)
{
_fileHandle.Dispose();
}
base.Dispose(disposing);
}
}
/// Flushes the OS buffer. This does not flush the internal read/write buffer.
private void FlushOSBuffer()
{
if (Interop.Sys.FSync(_fileHandle) < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
switch (errorInfo.Error)
{
case Interop.Error.EROFS:
case Interop.Error.EINVAL:
case Interop.Error.ENOTSUP:
// Ignore failures due to the FileStream being bound to a special file that
// doesn't support synchronization. In such cases there's nothing to flush.
break;
default:
throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
}
}
}
/// Writes any data in the write buffer to the underlying stream and resets the buffer.
private void FlushWriteBuffer()
{
AssertBufferInvariants();
if (_writePos > 0)
{
WriteNative(GetBuffer(), 0, _writePos);
_writePos = 0;
}
}
/// Asynchronously clears all buffers for this stream, causing any buffered data to be written to the underlying device.
/// The token to monitor for cancellation requests.
/// A task that represents the asynchronous flush operation.
private Task FlushAsyncInternal(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
if (_fileHandle.IsClosed)
{
throw Error.GetFileNotOpen();
}
// As with Win32FileStream, flush the buffers synchronously to avoid race conditions.
try
{
FlushInternalBuffer();
}
catch (Exception e)
{
return Task.FromException(e);
}
// We then separately flush to disk asynchronously. This is only
// necessary if we support writing; otherwise, we're done.
if (CanWrite)
{
return Task.Factory.StartNew(
state => ((FileStream)state).FlushOSBuffer(),
this,
cancellationToken,
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default);
}
else
{
return Task.CompletedTask;
}
}
/// Sets the length of this stream to the given value.
/// The new length of the stream.
private void SetLengthInternal(long value)
{
FlushInternalBuffer();
if (_appendStart != -1 && value < _appendStart)
{
throw new IOException(SR.IO_SetLengthAppendTruncate);
}
long origPos = _filePosition;
VerifyOSHandlePosition();
if (_filePosition != value)
{
SeekCore(value, SeekOrigin.Begin);
}
CheckFileCall(Interop.Sys.FTruncate(_fileHandle, value));
// 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);
}
}
}
/// Reads a block of bytes from the stream and writes the data in a given buffer.
///
/// When this method returns, contains the specified byte array with the values between offset and
/// (offset + count - 1) replaced by the bytes read from the current source.
///
/// The byte offset in array at which the read bytes will be placed.
/// The maximum number of bytes to read.
///
/// The total number of bytes read into the buffer. This might be less than the number of bytes requested
/// if that number of bytes are not currently available, or zero if the end of the stream is reached.
///
public override int Read(byte[] array, int offset, int count)
{
ValidateReadWriteArgs(array, offset, count);
if (_useAsyncIO)
{
_asyncState.Wait();
try { return ReadCore(array, offset, count); }
finally { _asyncState.Release(); }
}
else
{
return ReadCore(array, offset, count);
}
}
/// Reads a block of bytes from the stream and writes the data in a given buffer.
///
/// When this method returns, contains the specified byte array with the values between offset and
/// (offset + count - 1) replaced by the bytes read from the current source.
///
/// The byte offset in array at which the read bytes will be placed.
/// The maximum number of bytes to read.
///
/// The total number of bytes read into the buffer. This might be less than the number of bytes requested
/// if that number of bytes are not currently available, or zero if the end of the stream is reached.
///
private int ReadCore(byte[] array, int offset, int count)
{
PrepareForReading();
// Are there any bytes available in the read buffer? If yes,
// we can just return from the buffer. If the buffer is empty
// or has no more available data in it, we can either refill it
// (and then read from the buffer into the user's buffer) or
// we can just go directly into the user's buffer, if they asked
// for more data than we'd otherwise buffer.
int numBytesAvailable = _readLength - _readPos;
bool readFromOS = false;
if (numBytesAvailable == 0)
{
// If we're not able to seek, then we're not able to rewind the stream (i.e. flushing
// a read buffer), in which case we don't want to use a read buffer. Similarly, if
// the user has asked for more data than we can buffer, we also want to skip the buffer.
if (!CanSeek || (count >= _bufferLength))
{
// Read directly into the user's buffer
_readPos = _readLength = 0;
return ReadNative(array, offset, count);
}
else
{
// Read into our buffer.
_readLength = numBytesAvailable = ReadNative(GetBuffer(), 0, _bufferLength);
_readPos = 0;
if (numBytesAvailable == 0)
{
return 0;
}
// Note that we did an OS read as part of this Read, so that later
// we don't try to do one again if what's in the buffer doesn't
// meet the user's request.
readFromOS = true;
}
}
// Now that we know there's data in the buffer, read from it into the user's buffer.
Debug.Assert(numBytesAvailable > 0, "Data must be in the buffer to be here");
int bytesRead = Math.Min(numBytesAvailable, count);
Buffer.BlockCopy(GetBuffer(), _readPos, array, offset, bytesRead);
_readPos += bytesRead;
// We may not have had enough data in the buffer to completely satisfy the user's request.
// While Read doesn't require that we return as much data as the user requested (any amount
// up to the requested count is fine), FileStream on Windows tries to do so by doing a
// subsequent read from the file if we tried to satisfy the request with what was in the
// buffer but the buffer contained less than the requested count. To be consistent with that
// behavior, we do the same thing here on Unix. Note that we may still get less the requested
// amount, as the OS may give us back fewer than we request, either due to reaching the end of
// file, or due to its own whims.
if (!readFromOS && bytesRead < count)
{
Debug.Assert(_readPos == _readLength, "bytesToRead should only be < count if numBytesAvailable < count");
_readPos = _readLength = 0; // no data left in the read buffer
bytesRead += ReadNative(array, offset + bytesRead, count - bytesRead);
}
return bytesRead;
}
/// Unbuffered, reads a block of bytes from the stream and writes the data in a given buffer.
///
/// When this method returns, contains the specified byte array with the values between offset and
/// (offset + count - 1) replaced by the bytes read from the current source.
///
/// The byte offset in array at which the read bytes will be placed.
/// The maximum number of bytes to read.
///
/// The total number of bytes read into the buffer. This might be less than the number of bytes requested
/// if that number of bytes are not currently available, or zero if the end of the stream is reached.
///
private unsafe int ReadNative(byte[] array, int offset, int count)
{
FlushWriteBuffer(); // we're about to read; dump the write buffer
VerifyOSHandlePosition();
int bytesRead;
fixed (byte* bufPtr = array)
{
bytesRead = CheckFileCall(Interop.Sys.Read(_fileHandle, bufPtr + offset, count));
Debug.Assert(bytesRead <= count);
}
_filePosition += bytesRead;
return bytesRead;
}
///
/// Asynchronously reads a sequence of bytes from the current stream and advances
/// the position within the stream by the number of bytes read.
///
/// The buffer to write the data into.
/// The byte offset in buffer at which to begin writing data from the stream.
/// The maximum number of bytes to read.
/// The token to monitor for cancellation requests.
/// A task that represents the asynchronous read operation.
private Task ReadAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (_useAsyncIO)
{
if (!CanRead) // match Windows behavior; this gets thrown synchronously
{
throw Error.GetReadNotSupported();
}
// Serialize operations using the semaphore.
Task waitTask = _asyncState.WaitAsync();
// If we got ownership immediately, and if there's enough data in our buffer
// to satisfy the full request of the caller, hand back the buffered data.
// While it would be a legal implementation of the Read contract, we don't
// hand back here less than the amount requested so as to match the behavior
// in ReadCore that will make a native call to try to fulfill the remainder
// of the request.
if (waitTask.Status == TaskStatus.RanToCompletion)
{
int numBytesAvailable = _readLength - _readPos;
if (numBytesAvailable >= count)
{
try
{
PrepareForReading();
Buffer.BlockCopy(GetBuffer(), _readPos, buffer, offset, count);
_readPos += count;
return _asyncState._lastSuccessfulReadTask != null && _asyncState._lastSuccessfulReadTask.Result == count ?
_asyncState._lastSuccessfulReadTask :
(_asyncState._lastSuccessfulReadTask = Task.FromResult(count));
}
catch (Exception exc)
{
return Task.FromException(exc);
}
finally
{
_asyncState.Release();
}
}
}
// Otherwise, issue the whole request asynchronously.
_asyncState.Update(buffer, offset, count);
return waitTask.ContinueWith((t, s) =>
{
// The options available on Unix for writing asynchronously to an arbitrary file
// handle typically amount to just using another thread to do the synchronous write,
// which is exactly what this implementation does. This does mean there are subtle
// differences in certain FileStream behaviors between Windows and Unix when multiple
// asynchronous operations are issued against the stream to execute concurrently; on
// Unix the operations will be serialized due to the usage of a semaphore, but the
// position /length information won't be updated until after the write has completed,
// whereas on Windows it may happen before the write has completed.
Debug.Assert(t.Status == TaskStatus.RanToCompletion);
var thisRef = (FileStream)s;
try
{
byte[] b = thisRef._asyncState._buffer;
thisRef._asyncState._buffer = null; // remove reference to user's buffer
return thisRef.ReadCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
}
finally { thisRef._asyncState.Release(); }
}, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
}
else
{
return base.ReadAsync(buffer, offset, count, cancellationToken);
}
}
///
/// Reads a byte from the stream and advances the position within the stream
/// by one byte, or returns -1 if at the end of the stream.
///
/// The unsigned byte cast to an Int32, or -1 if at the end of the stream.
public override int ReadByte()
{
if (_useAsyncIO)
{
_asyncState.Wait();
try { return ReadByteCore(); }
finally { _asyncState.Release(); }
}
else
{
return ReadByteCore();
}
}
/// Writes a block of bytes to the file stream.
/// The buffer containing data to write to the stream.
/// The zero-based byte offset in array from which to begin copying bytes to the stream.
/// The maximum number of bytes to write.
public override void Write(byte[] array, int offset, int count)
{
ValidateReadWriteArgs(array, offset, count);
if (_useAsyncIO)
{
_asyncState.Wait();
try { WriteCore(array, offset, count); }
finally { _asyncState.Release(); }
}
else
{
WriteCore(array, offset, count);
}
}
/// Writes a block of bytes to the file stream.
/// The buffer containing data to write to the stream.
/// The zero-based byte offset in array from which to begin copying bytes to the stream.
/// The maximum number of bytes to write.
private void WriteCore(byte[] array, int offset, int count)
{
PrepareForWriting();
// If no data is being written, nothing more to do.
if (count == 0)
{
return;
}
// If there's already data in our write buffer, then we need to go through
// our buffer to ensure data isn't corrupted.
if (_writePos > 0)
{
// If there's space remaining in the buffer, then copy as much as
// we can from the user's buffer into ours.
int spaceRemaining = _bufferLength - _writePos;
if (spaceRemaining > 0)
{
int bytesToCopy = Math.Min(spaceRemaining, count);
Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, bytesToCopy);
_writePos += bytesToCopy;
// If we've successfully copied all of the user's data, we're done.
if (count == bytesToCopy)
{
return;
}
// Otherwise, keep track of how much more data needs to be handled.
offset += bytesToCopy;
count -= bytesToCopy;
}
// At this point, the buffer is full, so flush it out.
FlushWriteBuffer();
}
// Our buffer is now empty. If using the buffer would slow things down (because
// the user's looking to write more data than we can store in the buffer),
// skip the buffer. Otherwise, put the remaining data into the buffer.
Debug.Assert(_writePos == 0);
if (count >= _bufferLength)
{
WriteNative(array, offset, count);
}
else
{
Buffer.BlockCopy(array, offset, GetBuffer(), _writePos, count);
_writePos = count;
}
}
/// Unbuffered, writes a block of bytes to the file stream.
/// The buffer containing data to write to the stream.
/// The zero-based byte offset in array from which to begin copying bytes to the stream.
/// The maximum number of bytes to write.
private unsafe void WriteNative(byte[] array, int offset, int count)
{
VerifyOSHandlePosition();
fixed (byte* bufPtr = array)
{
while (count > 0)
{
int bytesWritten = CheckFileCall(Interop.Sys.Write(_fileHandle, bufPtr + offset, count));
Debug.Assert(bytesWritten <= count);
_filePosition += bytesWritten;
count -= bytesWritten;
offset += bytesWritten;
}
}
}
///
/// Asynchronously writes a sequence of bytes to the current stream, advances
/// the current position within this stream by the number of bytes written, and
/// monitors cancellation requests.
///
/// The buffer to write data from.
/// The zero-based byte offset in buffer from which to begin copying bytes to the stream.
/// The maximum number of bytes to write.
/// The token to monitor for cancellation requests.
/// A task that represents the asynchronous write operation.
private Task WriteAsyncInternal(byte[] buffer, int offset, int count, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled(cancellationToken);
if (_fileHandle.IsClosed)
throw Error.GetFileNotOpen();
if (_useAsyncIO)
{
if (!CanWrite) // match Windows behavior; this gets thrown synchronously
{
throw Error.GetWriteNotSupported();
}
// Serialize operations using the semaphore.
Task waitTask = _asyncState.WaitAsync();
// If we got ownership immediately, and if there's enough space in our buffer
// to buffer the entire write request, then do so and we're done.
if (waitTask.Status == TaskStatus.RanToCompletion)
{
int spaceRemaining = _bufferLength - _writePos;
if (spaceRemaining >= count)
{
try
{
PrepareForWriting();
Buffer.BlockCopy(buffer, offset, GetBuffer(), _writePos, count);
_writePos += count;
return Task.CompletedTask;
}
catch (Exception exc)
{
return Task.FromException(exc);
}
finally
{
_asyncState.Release();
}
}
}
// Otherwise, issue the whole request asynchronously.
_asyncState.Update(buffer, offset, count);
return waitTask.ContinueWith((t, s) =>
{
// The options available on Unix for writing asynchronously to an arbitrary file
// handle typically amount to just using another thread to do the synchronous write,
// which is exactly what this implementation does. This does mean there are subtle
// differences in certain FileStream behaviors between Windows and Unix when multiple
// asynchronous operations are issued against the stream to execute concurrently; on
// Unix the operations will be serialized due to the usage of a semaphore, but the
// position /length information won't be updated until after the write has completed,
// whereas on Windows it may happen before the write has completed.
Debug.Assert(t.Status == TaskStatus.RanToCompletion);
var thisRef = (FileStream)s;
try
{
byte[] b = thisRef._asyncState._buffer;
thisRef._asyncState._buffer = null; // remove reference to user's buffer
thisRef.WriteCore(b, thisRef._asyncState._offset, thisRef._asyncState._count);
}
finally { thisRef._asyncState.Release(); }
}, this, CancellationToken.None, TaskContinuationOptions.DenyChildAttach, TaskScheduler.Default);
}
else
{
return base.WriteAsync(buffer, offset, count, cancellationToken);
}
}
///
/// 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) // avoids an array allocation in the base implementation
{
if (_useAsyncIO)
{
_asyncState.Wait();
try { WriteByteCore(value); }
finally { _asyncState.Release(); }
}
else
{
WriteByteCore(value);
}
}
/// Prevents other processes from reading from or writing to the FileStream.
/// The beginning of the range to lock.
/// The range to be locked.
private void LockInternal(long position, long length)
{
CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_WRLCK));
}
/// Allows access by other processes to all or part of a file that was previously locked.
/// The beginning of the range to unlock.
/// The range to be unlocked.
private void UnlockInternal(long position, long length)
{
CheckFileCall(Interop.Sys.LockFileRegion(_fileHandle, position, length, Interop.Sys.LockType.F_UNLCK));
}
/// Sets the current position of this stream to the given value.
/// The point relative to origin from which to begin seeking.
///
/// Specifies the beginning, the end, or the current position as a reference
/// point for offset, using a value of type SeekOrigin.
///
/// The new position in the stream.
public override long Seek(long offset, SeekOrigin origin)
{
if (origin < SeekOrigin.Begin || origin > SeekOrigin.End)
{
throw new ArgumentException(SR.Argument_InvalidSeekOrigin, nameof(origin));
}
if (_fileHandle.IsClosed)
{
throw Error.GetFileNotOpen();
}
if (!CanSeek)
{
throw Error.GetSeekNotSupported();
}
VerifyOSHandlePosition();
// Flush our write/read buffer. FlushWrite will output any write buffer we have and reset _bufferWritePos.
// We don't call FlushRead, as that will do an unnecessary seek to rewind the read buffer, and since we're
// about to seek and update our position, we can simply update the offset as necessary and reset our read
// position and length to 0. (In the future, for some simple cases we could potentially add an optimization
// here to just move data around in the buffer for short jumps, to avoid re-reading the data from disk.)
FlushWriteBuffer();
if (origin == SeekOrigin.Current)
{
offset -= (_readLength - _readPos);
}
_readPos = _readLength = 0;
// Keep track of where we were, in case we're in append mode and need to verify
long oldPos = 0;
if (_appendStart >= 0)
{
oldPos = SeekCore(0, SeekOrigin.Current);
}
// Jump to the new location
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(SR.IO_SeekAppendOverwrite);
}
// Return the new position
return pos;
}
/// Sets the current position of this stream to the given value.
/// The point relative to origin from which to begin seeking.
///
/// Specifies the beginning, the end, or the current position as a reference
/// point for offset, using a value of type SeekOrigin.
///
/// The new position in the stream.
private long SeekCore(long offset, SeekOrigin origin)
{
Debug.Assert(!_fileHandle.IsClosed && (GetType() != typeof(FileStream) || CanSeek)); // verify that we can seek, but only if CanSeek won't be a virtual call (which could happen in the ctor)
Debug.Assert(origin >= SeekOrigin.Begin && origin <= SeekOrigin.End);
long pos = CheckFileCall(Interop.Sys.LSeek(_fileHandle, offset, (Interop.Sys.SeekWhence)(int)origin)); // SeekOrigin values are the same as Interop.libc.SeekWhence values
_filePosition = pos;
return pos;
}
private long CheckFileCall(long result, bool ignoreNotSupported = false)
{
if (result < 0)
{
Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo();
if (!(ignoreNotSupported && errorInfo.Error == Interop.Error.ENOTSUP))
{
throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false);
}
}
return result;
}
private int CheckFileCall(int result, bool ignoreNotSupported = false)
{
CheckFileCall((long)result, ignoreNotSupported);
return result;
}
/// State used when the stream is in async mode.
private sealed class AsyncState : SemaphoreSlim
{
/// The caller's buffer currently being used by the active async operation.
internal byte[] _buffer;
/// The caller's offset currently being used by the active async operation.
internal int _offset;
/// The caller's count currently being used by the active async operation.
internal int _count;
/// The last task successfully, synchronously returned task from ReadAsync.
internal Task _lastSuccessfulReadTask;
/// Initialize the AsyncState.
internal AsyncState() : base(initialCount: 1, maxCount: 1) { }
/// Sets the active buffer, offset, and count.
internal void Update(byte[] buffer, int offset, int count)
{
_buffer = buffer;
_offset = offset;
_count = count;
}
}
}
}