public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask; // no buffering = nothing to flush private unsafe int ReadSpan(Span <byte> destination) { if (!CanRead) { ThrowHelper.ThrowNotSupportedException_UnreadableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); NativeOverlapped nativeOverlapped = GetNativeOverlappedForCurrentPosition(); int r = FileStreamHelpers.ReadFileNative(_fileHandle, destination, true, &nativeOverlapped, out int errorCode); if (r == -1) { // For pipes, ERROR_BROKEN_PIPE is the normal end of the pipe. if (errorCode == Interop.Errors.ERROR_BROKEN_PIPE) { r = 0; } else { if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) { ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(_fileHandle)); } throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } Debug.Assert(r >= 0, "FileStream's ReadNative is likely broken."); _filePosition += r; return(r); }
private unsafe void WriteSpan(ReadOnlySpan <byte> source) { if (!CanWrite) { ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); NativeOverlapped nativeOverlapped = GetNativeOverlappedForCurrentPosition(); int r = FileStreamHelpers.WriteFileNative(_fileHandle, source, true, &nativeOverlapped, out int errorCode); if (r == -1) { // For pipes, ERROR_NO_DATA is not an error, but the pipe is closing. if (errorCode == Interop.Errors.ERROR_NO_DATA) { r = 0; } else { // ERROR_INVALID_PARAMETER may be returned for writes // where the position is too large or for synchronous writes // to a handle opened asynchronously. if (errorCode == Interop.Errors.ERROR_INVALID_PARAMETER) { throw new IOException(SR.IO_FileTooLongOrHandleNotSync); } throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } Debug.Assert(r >= 0, "FileStream's WriteCore is likely broken."); _filePosition += r; UpdateLengthOnChangePosition(); }
internal LegacyFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) { string fullPath = Path.GetFullPath(path); _path = fullPath; _access = access; _bufferLength = bufferSize; if ((options & FileOptions.Asynchronous) != 0) { _useAsyncIO = true; } _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options); try { Init(mode, share, path, options); } catch { // If anything goes wrong while setting up the stream, make sure we deterministically dispose // of the opened handle. _fileHandle.Dispose(); _fileHandle = null !; throw; } }
protected sealed override void Dispose(bool disposing) { if (_strategy.IsClosed) { return; } try { Flush(); } catch (Exception e) when(!disposing && FileStreamHelpers.IsIoRelatedException(e)) { // On finalization, ignore failures from trying to flush the write buffer, // e.g. if this stream is wrapping a pipe and the pipe is now broken. } finally { // Don't set the buffer to null, to avoid a NullReferenceException // when users have a race condition in their code (i.e. they call // FileStream.Close when calling another method on FileStream like Read). // There is no need to call base.Dispose as it's empty _writePos = 0; _strategy.DisposeInternal(disposing); } }
internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) { string fullPath = Path.GetFullPath(path); _path = fullPath; _access = access; _share = share; _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options); try { _canSeek = true; Init(mode, 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; } }
private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); Debug.Assert(CanRead, "_parent.CanRead"); bool canSeek = CanSeek; if (canSeek) { VerifyOSHandlePosition(); } try { await FileStreamHelpers .AsyncModeCopyToAsync(_fileHandle, _path, canSeek, _filePosition, destination, bufferSize, cancellationToken) .ConfigureAwait(false); } finally { // Make sure the stream's current position reflects where we ended up if (!_fileHandle.IsClosed && canSeek) { SeekCore(_fileHandle, 0, SeekOrigin.End); } } }
public sealed override void Flush() => Flush(flushToDisk: false); // we have nothing to flush as there is no buffer here internal sealed override void Flush(bool flushToDisk) { if (flushToDisk && CanWrite) { FileStreamHelpers.FlushToDisk(_fileHandle, _path); } }
internal override void Flush(bool flushToDisk) { FlushInternalBuffer(); if (flushToDisk && CanWrite) { FileStreamHelpers.FlushToDisk(_fileHandle); } }
// We absolutely need this method broken out so that WriteInternalCoreAsync can call // a method without having to go through buffering code that might call FlushWrite. protected unsafe void SetLengthCore(long value) { Debug.Assert(value >= 0, "value >= 0"); VerifyOSHandlePosition(); FileStreamHelpers.SetFileLength(_fileHandle, _path, value); if (_filePosition > value) { SeekCore(_fileHandle, 0, SeekOrigin.End); } }
/// <summary>Releases the unmanaged resources used by the stream.</summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { try { if (_fileHandle != null && !_fileHandle.IsClosed) { // Flush any remaining data in the file try { FlushWriteBuffer(); } catch (Exception e) when(!disposing && FileStreamHelpers.IsIoRelatedException(e)) { // On finalization, ignore failures from trying to flush the write buffer, // e.g. if this stream is wrapping a pipe and the pipe is now broken. } // 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 } // Closing the file handle can fail, e.g. due to out of disk space // Throw these errors as exceptions when disposing if (_fileHandle != null && !_fileHandle.IsClosed && disposing) { SafeFileHandle.t_lastCloseErrorInfo = null; _fileHandle.Dispose(); if (SafeFileHandle.t_lastCloseErrorInfo != null) { throw Interop.GetExceptionForIoErrno(SafeFileHandle.t_lastCloseErrorInfo.GetValueOrDefault(), _path, isDirectory: false); } } } } finally { if (_fileHandle != null && !_fileHandle.IsClosed) { _fileHandle.Dispose(); } base.Dispose(disposing); } }
private void InitFromHandleImpl(SafeFileHandle handle, out bool canSeek, out bool isPipe) { FileStreamHelpers.GetFileTypeSpecificInformation(handle, out canSeek, out isPipe); OnInitFromHandle(handle); if (_canSeek) { SeekCore(handle, 0, SeekOrigin.Current); } else { _filePosition = 0; } }
private void Init(FileMode mode, string originalPath) { FileStreamHelpers.ValidateFileTypeForNonExtendedPaths(_fileHandle, originalPath); OnInit(); // For Append mode... if (mode == FileMode.Append) { _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End); } else { _appendStart = -1; } }
protected override void OnInitFromHandle(SafeFileHandle handle) { // As we can accurately check the handle type when we have access to NtQueryInformationFile we don't need to skip for // any particular file handle type. // If the handle was passed in without an explicit async setting, we already looked it up in GetDefaultIsAsync if (!handle.IsAsync.HasValue) { return; } // If we can't check the handle, just assume it is ok. if (!(FileStreamHelpers.IsHandleSynchronous(handle, ignoreInvalid: false) ?? true)) { ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(handle)); } }
private long _appendStart; // When appending, prevent overwriting file. internal OSFileStreamStrategy(SafeFileHandle handle, FileAccess access) { _access = access; handle.EnsureThreadPoolBindingInitialized(); if (handle.CanSeek) { // given strategy was created out of existing handle, so we have to perform // a syscall to get the current handle offset _filePosition = FileStreamHelpers.Seek(handle, 0, SeekOrigin.Current); } else { _filePosition = 0; } _fileHandle = handle; }
private async Task AsyncModeCopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) { Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); Debug.Assert(CanRead, "_parent.CanRead"); try { await FileStreamHelpers .AsyncModeCopyToAsync(_fileHandle, _path, CanSeek, _filePosition, destination, bufferSize, cancellationToken) .ConfigureAwait(false); } finally { // Make sure the stream's current position reflects where we ended up if (!_fileHandle.IsClosed && CanSeek) { _filePosition = Length; } } }
/// <summary>Releases the unmanaged resources used by the stream.</summary> /// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param> protected override void Dispose(bool disposing) { try { if (_fileHandle != null && !_fileHandle.IsClosed) { // Flush any remaining data in the file try { FlushWriteBuffer(); } catch (Exception e) when(!disposing && FileStreamHelpers.IsIoRelatedException(e)) { // On finalization, ignore failures from trying to flush the write buffer, // e.g. if this stream is wrapping a pipe and the pipe is now broken. } // Closing the file handle can fail, e.g. due to out of disk space // Throw these errors as exceptions when disposing if (_fileHandle != null && !_fileHandle.IsClosed && disposing) { SafeFileHandle.t_lastCloseErrorInfo = null; _fileHandle.Dispose(); if (SafeFileHandle.t_lastCloseErrorInfo != null) { throw Interop.GetExceptionForIoErrno(SafeFileHandle.t_lastCloseErrorInfo.GetValueOrDefault(), _fileHandle.Path, isDirectory: false); } } } } finally { if (_fileHandle != null && !_fileHandle.IsClosed) { _fileHandle.Dispose(); } base.Dispose(disposing); } }
private bool _exposedHandle; // created from handle, or SafeFileHandle was used and the handle got exposed internal WindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access, FileShare share) { _access = access; _share = share; _exposedHandle = true; handle.InitThreadPoolBindingIfNeeded(); if (handle.CanSeek) { // given strategy was created out of existing handle, so we have to perform // a syscall to get the current handle offset _filePosition = FileStreamHelpers.Seek(handle, _path, 0, SeekOrigin.Current); } else { _filePosition = 0; } _fileHandle = handle; }
private unsafe ValueTask WriteAsyncInternal(ReadOnlyMemory <byte> source, CancellationToken cancellationToken) { if (!CanWrite) { ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // valueTaskSource is not null when: // - First time calling WriteAsync in buffered mode // - Second+ time calling WriteAsync, both buffered or unbuffered // - On buffered flush, when source memory is also the internal buffer // valueTaskSource is null when: // - First time calling WriteAsync in unbuffered mode ValueTaskSource valueTaskSource = Interlocked.Exchange(ref _reusableValueTaskSource, null) ?? new ValueTaskSource(this); NativeOverlapped *intOverlapped = valueTaskSource.Configure(source); long positionBefore = _filePosition; if (CanSeek) { // Now set the position to read from in the NativeOverlapped struct // For pipes, we should leave the offset fields set to 0. intOverlapped->OffsetLow = (int)positionBefore; intOverlapped->OffsetHigh = (int)(positionBefore >> 32); // When using overlapped IO, the OS is not supposed to // touch the file pointer location at all. We will adjust it // ourselves, but only in memory. This isn't threadsafe. _filePosition += source.Length; UpdateLengthOnChangePosition(); } // queue an async WriteFile operation and pass in a packed overlapped int r = FileStreamHelpers.WriteFileNative(_fileHandle, source.Span, false, intOverlapped, out int errorCode); // WriteFile, the OS version, will return 0 on failure. But // my WriteFileNative wrapper returns -1. My wrapper will return // the following: // On error, r==-1. // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING // On async requests that completed sequentially, r==0 // You will NEVER RELIABLY be able to get the number of bytes // written back from this call when using overlapped IO! You must // not pass in a non-null lpNumBytesWritten to WriteFile when using // overlapped structures! This is ByDesign NT behavior. if (r == -1) { // For pipes, when they are closed on the other side, they will come here. if (errorCode == Interop.Errors.ERROR_NO_DATA) { // Not an error, but EOF. AsyncFSCallback will NOT be called. // Completing TCS and return cached task allowing the GC to collect TCS. valueTaskSource.ReleaseNativeResource(); TryToReuse(valueTaskSource); return(ValueTask.CompletedTask); } else if (errorCode != Interop.Errors.ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { _filePosition = positionBefore; } valueTaskSource.ReleaseNativeResource(); TryToReuse(valueTaskSource); if (errorCode == Interop.Errors.ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } else { throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation valueTaskSource.RegisterForCancellation(cancellationToken); } } else { // Due to a workaround for a race condition in NT's ReadFile & // WriteFile routines, we will always be returning 0 from WriteFileNative // when we do async IO instead of the number of bytes written, // irregardless of whether the operation completed // synchronously or asynchronously. We absolutely must not // set asyncResult._numBytes here, since will never have correct // results. } return(new ValueTask(valueTaskSource, valueTaskSource.Version)); }
private unsafe ValueTask <int> ReadAsyncInternal(Memory <byte> destination, CancellationToken cancellationToken = default) { if (!CanRead) { ThrowHelper.ThrowNotSupportedException_UnreadableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // valueTaskSource is not null when: // - First time calling ReadAsync in buffered mode // - Second+ time calling ReadAsync, both buffered or unbuffered // - On buffered flush, when source memory is also the internal buffer // valueTaskSource is null when: // - First time calling ReadAsync in unbuffered mode ValueTaskSource valueTaskSource = Interlocked.Exchange(ref _reusableValueTaskSource, null) ?? new ValueTaskSource(this); NativeOverlapped *intOverlapped = valueTaskSource.Configure(destination); // Calculate position in the file we should be at after the read is done long positionBefore = _filePosition; if (CanSeek) { long len = Length; if (positionBefore + destination.Length > len) { if (positionBefore <= len) { destination = destination.Slice(0, (int)(len - positionBefore)); } else { destination = default; } } // Now set the position to read from in the NativeOverlapped struct // For pipes, we should leave the offset fields set to 0. intOverlapped->OffsetLow = unchecked ((int)positionBefore); intOverlapped->OffsetHigh = (int)(positionBefore >> 32); // When using overlapped IO, the OS is not supposed to // touch the file pointer location at all. We will adjust it // ourselves, but only in memory. This isn't threadsafe. _filePosition += destination.Length; } // queue an async ReadFile operation and pass in a packed overlapped int r = FileStreamHelpers.ReadFileNative(_fileHandle, destination.Span, false, intOverlapped, out int errorCode); // ReadFile, the OS version, will return 0 on failure. But // my ReadFileNative wrapper returns -1. My wrapper will return // the following: // On error, r==-1. // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING // on async requests that completed sequentially, r==0 // You will NEVER RELIABLY be able to get the number of bytes // read back from this call when using overlapped structures! You must // not pass in a non-null lpNumBytesRead to ReadFile when using // overlapped structures! This is by design NT behavior. if (r == -1) { // For pipes, when they hit EOF, they will come here. if (errorCode == Interop.Errors.ERROR_BROKEN_PIPE) { // Not an error, but EOF. AsyncFSCallback will NOT be // called. Call the user callback here. // We clear the overlapped status bit for this special case. // Failure to do so looks like we are freeing a pending overlapped later. intOverlapped->InternalLow = IntPtr.Zero; valueTaskSource.ReleaseNativeResource(); TryToReuse(valueTaskSource); return(new ValueTask <int>(0)); } else if (errorCode != Interop.Errors.ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { _filePosition = positionBefore; } valueTaskSource.ReleaseNativeResource(); TryToReuse(valueTaskSource); if (errorCode == Interop.Errors.ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } else { throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation valueTaskSource.RegisterForCancellation(cancellationToken); } } else { // Due to a workaround for a race condition in NT's ReadFile & // WriteFile routines, we will always be returning 0 from ReadFileNative // when we do async IO instead of the number of bytes read, // irregardless of whether the operation completed // synchronously or asynchronously. We absolutely must not // set asyncResult._numBytes here, since will never have correct // results. } return(new ValueTask <int>(valueTaskSource, valueTaskSource.Version)); }
// This doesn't do argument checking. Necessary for SetLength, which must // set the file pointer beyond the end of the file. This will update the // internal position private long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, bool closeInvalidHandle = false) { Debug.Assert(fileHandle.CanSeek, "fileHandle.CanSeek"); return(_filePosition = FileStreamHelpers.Seek(fileHandle, offset, origin, closeInvalidHandle)); }
internal sealed override void Lock(long position, long length) => FileStreamHelpers.Lock(_fileHandle, _path, position, length);
private unsafe Task <int> ReadAsyncInternal(Memory <byte> destination, CancellationToken cancellationToken = default) { if (!CanRead) { ThrowHelper.ThrowNotSupportedException_UnreadableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // Create and store async stream class library specific data in the async result FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, 0, destination); NativeOverlapped * intOverlapped = completionSource.Overlapped; // Calculate position in the file we should be at after the read is done if (CanSeek) { long len = Length; // Make sure we are reading from the position that we think we are VerifyOSHandlePosition(); if (destination.Length > len - _filePosition) { if (_filePosition <= len) { destination = destination.Slice(0, (int)(len - _filePosition)); } else { destination = default; } } // Now set the position to read from in the NativeOverlapped struct // For pipes, we should leave the offset fields set to 0. intOverlapped->OffsetLow = unchecked ((int)_filePosition); intOverlapped->OffsetHigh = (int)(_filePosition >> 32); // When using overlapped IO, the OS is not supposed to // touch the file pointer location at all. We will adjust it // ourselves. This isn't threadsafe. // WriteFile should not update the file pointer when writing // in overlapped mode, according to MSDN. But it does update // the file pointer when writing to a UNC path! // So changed the code below to seek to an absolute // location, not a relative one. ReadFile seems consistent though. SeekCore(_fileHandle, destination.Length, SeekOrigin.Current); } // queue an async ReadFile operation and pass in a packed overlapped int r = FileStreamHelpers.ReadFileNative(_fileHandle, destination.Span, intOverlapped, out int errorCode); // ReadFile, the OS version, will return 0 on failure. But // my ReadFileNative wrapper returns -1. My wrapper will return // the following: // On error, r==-1. // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING // on async requests that completed sequentially, r==0 // You will NEVER RELIABLY be able to get the number of bytes // read back from this call when using overlapped structures! You must // not pass in a non-null lpNumBytesRead to ReadFile when using // overlapped structures! This is by design NT behavior. if (r == -1) { // For pipes, when they hit EOF, they will come here. if (errorCode == ERROR_BROKEN_PIPE) { // Not an error, but EOF. AsyncFSCallback will NOT be // called. Call the user callback here. // We clear the overlapped status bit for this special case. // Failure to do so looks like we are freeing a pending overlapped later. intOverlapped->InternalLow = IntPtr.Zero; completionSource.SetCompletedSynchronously(0); } else if (errorCode != ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { SeekCore(_fileHandle, 0, SeekOrigin.Current); } completionSource.ReleaseNativeResource(); if (errorCode == ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } else { throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation completionSource.RegisterForCancellation(cancellationToken); } } else { // Due to a workaround for a race condition in NT's ReadFile & // WriteFile routines, we will always be returning 0 from ReadFileNative // when we do async IO instead of the number of bytes read, // irregardless of whether the operation completed // synchronously or asynchronously. We absolutely must not // set asyncResult._numBytes here, since will never have correct // results. } return(completionSource.Task); }
private unsafe Task WriteAsyncInternalCore(ReadOnlyMemory <byte> source, CancellationToken cancellationToken) { if (!CanWrite) { ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } Debug.Assert(!_fileHandle.IsClosed, "!_handle.IsClosed"); // Create and store async stream class library specific data in the async result FileStreamCompletionSource completionSource = FileStreamCompletionSource.Create(this, _preallocatedOverlapped, 0, source); NativeOverlapped * intOverlapped = completionSource.Overlapped; if (CanSeek) { // Make sure we set the length of the file appropriately. long len = Length; // Make sure we are writing to the position that we think we are VerifyOSHandlePosition(); if (_filePosition + source.Length > len) { SetLengthCore(_filePosition + source.Length); } // Now set the position to read from in the NativeOverlapped struct // For pipes, we should leave the offset fields set to 0. intOverlapped->OffsetLow = (int)_filePosition; intOverlapped->OffsetHigh = (int)(_filePosition >> 32); // When using overlapped IO, the OS is not supposed to // touch the file pointer location at all. We will adjust it // ourselves. This isn't threadsafe. SeekCore(_fileHandle, source.Length, SeekOrigin.Current); } // queue an async WriteFile operation and pass in a packed overlapped int r = FileStreamHelpers.WriteFileNative(_fileHandle, source.Span, intOverlapped, out int errorCode); // WriteFile, the OS version, will return 0 on failure. But // my WriteFileNative wrapper returns -1. My wrapper will return // the following: // On error, r==-1. // On async requests that are still pending, r==-1 w/ errorCode==ERROR_IO_PENDING // On async requests that completed sequentially, r==0 // You will NEVER RELIABLY be able to get the number of bytes // written back from this call when using overlapped IO! You must // not pass in a non-null lpNumBytesWritten to WriteFile when using // overlapped structures! This is ByDesign NT behavior. if (r == -1) { // For pipes, when they are closed on the other side, they will come here. if (errorCode == ERROR_NO_DATA) { // Not an error, but EOF. AsyncFSCallback will NOT be called. // Completing TCS and return cached task allowing the GC to collect TCS. completionSource.SetCompletedSynchronously(0); return(Task.CompletedTask); } else if (errorCode != ERROR_IO_PENDING) { if (!_fileHandle.IsClosed && CanSeek) // Update Position - It could be anywhere. { SeekCore(_fileHandle, 0, SeekOrigin.Current); } completionSource.ReleaseNativeResource(); if (errorCode == ERROR_HANDLE_EOF) { ThrowHelper.ThrowEndOfFileException(); } else { throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } } else if (cancellationToken.CanBeCanceled) // ERROR_IO_PENDING { // Only once the IO is pending do we register for cancellation completionSource.RegisterForCancellation(cancellationToken); } } else { // Due to a workaround for a race condition in NT's ReadFile & // WriteFile routines, we will always be returning 0 from WriteFileNative // when we do async IO instead of the number of bytes written, // irregardless of whether the operation completed // synchronously or asynchronously. We absolutely must not // set asyncResult._numBytes here, since will never have correct // results. } return(completionSource.Task); }
/// <summary>Allows access by other processes to all or part of a file that was previously locked.</summary> /// <param name="position">The beginning of the range to unlock.</param> /// <param name="length">The range to be unlocked.</param> internal override void Unlock(long position, long length) => FileStreamHelpers.Unlock(_fileHandle, position, length);
/// <summary>Prevents other processes from reading from or writing to the FileStream.</summary> /// <param name="position">The beginning of the range to lock.</param> /// <param name="length">The range to be locked.</param> internal override void Lock(long position, long length) => FileStreamHelpers.Lock(_fileHandle, CanWrite, position, length);
private unsafe ValueTask WriteAsyncInternal(ReadOnlyMemory <byte> source, CancellationToken cancellationToken) { if (!CanWrite) { ThrowHelper.ThrowNotSupportedException_UnwritableStream(); } // Rent the reusable ValueTaskSource, or create a new one to use if we couldn't get one (which // should only happen on first use or if the FileStream is being used concurrently). ValueTaskSource vts = Interlocked.Exchange(ref _reusableValueTaskSource, null) ?? new ValueTaskSource(this); try { NativeOverlapped *nativeOverlapped = vts.PrepareForOperation(source); Debug.Assert(vts._memoryHandle.Pointer != null); long positionBefore = _filePosition; if (CanSeek) { // Now set the position to read from in the NativeOverlapped struct // For pipes, we should leave the offset fields set to 0. nativeOverlapped->OffsetLow = (int)positionBefore; nativeOverlapped->OffsetHigh = (int)(positionBefore >> 32); // When using overlapped IO, the OS is not supposed to // touch the file pointer location at all. We will adjust it // ourselves, but only in memory. This isn't threadsafe. _filePosition += source.Length; UpdateLengthOnChangePosition(); } // Queue an async WriteFile operation. if (Interop.Kernel32.WriteFile(_fileHandle, (byte *)vts._memoryHandle.Pointer, source.Length, IntPtr.Zero, nativeOverlapped) == 0) { // The operation failed, or it's pending. int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(_fileHandle); if (errorCode == Interop.Errors.ERROR_IO_PENDING) { // Common case: IO was initiated, completion will be handled by callback. // Register for cancellation now that the operation has been initiated. vts.RegisterForCancellation(cancellationToken); } else { // Error. Callback will not be invoked. vts.Dispose(); return(errorCode == Interop.Errors.ERROR_NO_DATA ? // EOF on a pipe. IO callback will not be called. ValueTask.CompletedTask : ValueTask.FromException(HandleIOError(positionBefore, errorCode))); } } } catch { vts.Dispose(); throw; } // Completion handled by callback. vts.FinishedScheduling(); return(new ValueTask(vts, vts.Version)); }
private unsafe ValueTask <int> ReadAsyncInternal(Memory <byte> destination, CancellationToken cancellationToken = default) { if (!CanRead) { ThrowHelper.ThrowNotSupportedException_UnreadableStream(); } // Rent the reusable ValueTaskSource, or create a new one to use if we couldn't get one (which // should only happen on first use or if the FileStream is being used concurrently). ValueTaskSource vts = Interlocked.Exchange(ref _reusableValueTaskSource, null) ?? new ValueTaskSource(this); try { NativeOverlapped *nativeOverlapped = vts.PrepareForOperation(destination); Debug.Assert(vts._memoryHandle.Pointer != null); // Calculate position in the file we should be at after the read is done long positionBefore = _filePosition; if (CanSeek) { long len = Length; if (positionBefore + destination.Length > len) { destination = positionBefore <= len? destination.Slice(0, (int)(len - positionBefore)) : default; } // Now set the position to read from in the NativeOverlapped struct // For pipes, we should leave the offset fields set to 0. nativeOverlapped->OffsetLow = unchecked ((int)positionBefore); nativeOverlapped->OffsetHigh = (int)(positionBefore >> 32); // When using overlapped IO, the OS is not supposed to // touch the file pointer location at all. We will adjust it // ourselves, but only in memory. This isn't threadsafe. _filePosition += destination.Length; } // Queue an async ReadFile operation. if (Interop.Kernel32.ReadFile(_fileHandle, (byte *)vts._memoryHandle.Pointer, destination.Length, IntPtr.Zero, nativeOverlapped) == 0) { // The operation failed, or it's pending. int errorCode = FileStreamHelpers.GetLastWin32ErrorAndDisposeHandleIfInvalid(_fileHandle); switch (errorCode) { case Interop.Errors.ERROR_IO_PENDING: // Common case: IO was initiated, completion will be handled by callback. // Register for cancellation now that the operation has been initiated. vts.RegisterForCancellation(cancellationToken); break; case Interop.Errors.ERROR_BROKEN_PIPE: // EOF on a pipe. Callback will not be called. // We clear the overlapped status bit for this special case (failure // to do so looks like we are freeing a pending overlapped later). nativeOverlapped->InternalLow = IntPtr.Zero; vts.Dispose(); return(ValueTask.FromResult(0)); default: // Error. Callback will not be called. vts.Dispose(); return(ValueTask.FromException <int>(HandleIOError(positionBefore, errorCode))); } } } catch { vts.Dispose(); throw; } // Completion handled by callback. vts.FinishedScheduling(); return(new ValueTask <int>(vts, vts.Version)); }
/// <summary>Initializes a stream for reading or writing a Unix file.</summary> /// <param name="mode">How the file should be opened.</param> /// <param name="share">What other access to the file should be allowed. This is currently ignored.</param> /// <param name="originalPath">The original path specified for the FileStream.</param> /// <param name="options">Options, passed via arguments as we have no guarantee that _options field was already set.</param> /// <param name="preallocationSize">passed to posix_fallocate</param> private void Init(FileMode mode, FileShare share, string originalPath, FileOptions options, long preallocationSize) { // 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(); } _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. } if (mode == FileMode.Append) { // Jump to the end of the file if opened as Append. _appendStart = SeekCore(_fileHandle, 0, SeekOrigin.End); } else if (mode == FileMode.Create || mode == FileMode.Truncate) { // Truncate the file now if the file mode requires it. This ensures that the file only will be truncated // if opened successfully. if (Interop.Sys.FTruncate(_fileHandle, 0) < 0) { Interop.ErrorInfo errorInfo = Interop.Sys.GetLastErrorInfo(); if (errorInfo.Error != Interop.Error.EBADF && errorInfo.Error != Interop.Error.EINVAL) { // We know the file descriptor is valid and we know the size argument to FTruncate is correct, // so if EBADF or EINVAL is returned, it means we're dealing with a special file that can't be // truncated. Ignore the error in such cases; in all others, throw. throw Interop.GetExceptionForIoErrno(errorInfo, _path, isDirectory: false); } } } // If preallocationSize has been provided for a creatable and writeable file if (FileStreamHelpers.ShouldPreallocate(preallocationSize, _access, mode)) { int fallocateResult = Interop.Sys.PosixFAllocate(_fileHandle, 0, preallocationSize); if (fallocateResult != 0) { _fileHandle.Dispose(); Interop.Sys.Unlink(_path !); // remove the file to mimic Windows behaviour (atomic operation) if (fallocateResult == -1) { throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, _path, preallocationSize)); } Debug.Assert(fallocateResult == -2); throw new IOException(SR.Format(SR.IO_FileTooLarge_Path_AllocationSize, _path, preallocationSize)); } } }
// This doesn't do argument checking. Necessary for SetLength, which must // set the file pointer beyond the end of the file. This will update the // internal position protected long SeekCore(SafeFileHandle fileHandle, long offset, SeekOrigin origin, bool closeInvalidHandle = false) { Debug.Assert(!fileHandle.IsClosed && _canSeek, "!fileHandle.IsClosed && _canSeek"); return(_filePosition = FileStreamHelpers.Seek(fileHandle, _path, offset, origin, closeInvalidHandle)); }