// 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; } } } }