diff --git a/src/libraries/System.IO.FileSystem/tests/LegacyTests/runtimeconfig.template.json b/src/libraries/System.IO.FileSystem/tests/LegacyTests/runtimeconfig.template.json index 010891ed8fc6c..0c1a3482aba4f 100644 --- a/src/libraries/System.IO.FileSystem/tests/LegacyTests/runtimeconfig.template.json +++ b/src/libraries/System.IO.FileSystem/tests/LegacyTests/runtimeconfig.template.json @@ -1,5 +1,5 @@ { "configProperties": { - "System.IO.UseLegacyFileStream": true + "System.IO.UseNet5CompatFileStream": false } } diff --git a/src/libraries/System.IO/tests/LegacyTests/runtimeconfig.template.json b/src/libraries/System.IO/tests/LegacyTests/runtimeconfig.template.json index 010891ed8fc6c..0c1a3482aba4f 100644 --- a/src/libraries/System.IO/tests/LegacyTests/runtimeconfig.template.json +++ b/src/libraries/System.IO/tests/LegacyTests/runtimeconfig.template.json @@ -1,5 +1,5 @@ { "configProperties": { - "System.IO.UseLegacyFileStream": true + "System.IO.UseNet5CompatFileStream": false } } diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index 7aed1af79c2b2..44f01e2821e27 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -392,8 +392,6 @@ - - @@ -405,12 +403,10 @@ - - @@ -426,6 +422,11 @@ + + + + + @@ -1635,17 +1636,17 @@ - - - - - - + + + + + + @@ -1846,13 +1847,13 @@ - - - - + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs index 20e5f73bb0efd..439f00c0c5c8f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO.Strategies; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Threading; @@ -46,7 +47,7 @@ public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferS { ValidateHandle(safeHandle, access, bufferSize, isAsync); - _strategy = WrapIfDerivedType(FileStreamHelpers.ChooseStrategy(safeHandle, access, bufferSize, isAsync)); + _strategy = FileStreamHelpers.ChooseStrategy(this, safeHandle, access, bufferSize, isAsync); } catch { @@ -65,22 +66,27 @@ public FileStream(IntPtr handle, FileAccess access, bool ownsHandle, int bufferS private static void ValidateHandle(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) { if (handle.IsInvalid) + { throw new ArgumentException(SR.Arg_InvalidHandle, nameof(handle)); - - if (access < FileAccess.Read || access > FileAccess.ReadWrite) + } + else if (access < FileAccess.Read || access > FileAccess.ReadWrite) + { throw new ArgumentOutOfRangeException(nameof(access), SR.ArgumentOutOfRange_Enum); - if (bufferSize <= 0) + } + else if (bufferSize <= 0) + { throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); - - if (handle.IsClosed) + } + else if (handle.IsClosed) + { ThrowHelper.ThrowObjectDisposedException_FileClosed(); - if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.GetValueOrDefault()) + } + else if (handle.IsAsync.HasValue && isAsync != handle.IsAsync.GetValueOrDefault()) + { throw new ArgumentException(SR.Arg_HandleNotAsync, nameof(handle)); + } } - private FileStreamStrategy WrapIfDerivedType(FileStreamStrategy impl) - => GetType() == typeof(FileStream) ? impl : new DerivedFileStreamStrategy(this, impl); - public FileStream(SafeFileHandle handle, FileAccess access) : this(handle, access, DefaultBufferSize) { @@ -95,56 +101,76 @@ public FileStream(SafeFileHandle handle, FileAccess access, int bufferSize, bool { ValidateHandle(handle, access, bufferSize, isAsync); - _strategy = WrapIfDerivedType(FileStreamHelpers.ChooseStrategy(handle, access, bufferSize, isAsync)); + _strategy = FileStreamHelpers.ChooseStrategy(this, handle, access, bufferSize, isAsync); } - public FileStream(string path, FileMode mode) : - this(path, mode, mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite, DefaultShare, DefaultBufferSize, DefaultIsAsync) - { } + public FileStream(string path, FileMode mode) + : this(path, mode, mode == FileMode.Append ? FileAccess.Write : FileAccess.ReadWrite, DefaultShare, DefaultBufferSize, DefaultIsAsync) + { + } - public FileStream(string path, FileMode mode, FileAccess access) : - this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync) - { } + public FileStream(string path, FileMode mode, FileAccess access) + : this(path, mode, access, DefaultShare, DefaultBufferSize, DefaultIsAsync) + { + } - public FileStream(string path, FileMode mode, FileAccess access, FileShare share) : - this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync) - { } + public FileStream(string path, FileMode mode, FileAccess access, FileShare share) + : this(path, mode, access, share, DefaultBufferSize, DefaultIsAsync) + { + } - public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) : - this(path, mode, access, share, bufferSize, DefaultIsAsync) - { } + public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize) + : this(path, mode, access, share, bufferSize, DefaultIsAsync) + { + } - public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) : - this(path, mode, access, share, bufferSize, useAsync ? FileOptions.Asynchronous : FileOptions.None) - { } + public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, bool useAsync) + : this(path, mode, access, share, bufferSize, useAsync ? FileOptions.Asynchronous : FileOptions.None) + { + } public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) { if (path == null) + { throw new ArgumentNullException(nameof(path), SR.ArgumentNull_Path); - if (path.Length == 0) + } + else if (path.Length == 0) + { throw new ArgumentException(SR.Argument_EmptyPath, nameof(path)); + } // don't include inheritable in our bounds check for share FileShare tempshare = share & ~FileShare.Inheritable; string? badArg = null; if (mode < FileMode.CreateNew || mode > FileMode.Append) + { badArg = nameof(mode); + } else if (access < FileAccess.Read || access > FileAccess.ReadWrite) + { badArg = nameof(access); + } else if (tempshare < FileShare.None || tempshare > (FileShare.ReadWrite | FileShare.Delete)) + { badArg = nameof(share); + } if (badArg != null) + { throw new ArgumentOutOfRangeException(badArg, SR.ArgumentOutOfRange_Enum); + } // NOTE: any change to FileOptions enum needs to be matched here in the error validation if (options != FileOptions.None && (options & ~(FileOptions.WriteThrough | FileOptions.Asynchronous | FileOptions.RandomAccess | FileOptions.DeleteOnClose | FileOptions.SequentialScan | FileOptions.Encrypted | (FileOptions)0x20000000 /* NoBuffering */)) != 0) + { throw new ArgumentOutOfRangeException(nameof(options), SR.ArgumentOutOfRange_Enum); - - if (bufferSize <= 0) + } + else if (bufferSize <= 0) + { throw new ArgumentOutOfRangeException(nameof(bufferSize), SR.ArgumentOutOfRange_NeedPosNum); + } // Write access validation if ((access & FileAccess.Write) == 0) @@ -157,14 +183,15 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share } if ((access & FileAccess.Read) != 0 && mode == FileMode.Append) + { throw new ArgumentException(SR.Argument_InvalidAppendMode, nameof(access)); - - if ((access & FileAccess.Write) == FileAccess.Write) + } + else if ((access & FileAccess.Write) == FileAccess.Write) { SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch); } - _strategy = WrapIfDerivedType(FileStreamHelpers.ChooseStrategy(path, mode, access, share, bufferSize, options)); + _strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options); } [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. https://go.microsoft.com/fwlink/?linkid=14202")] @@ -177,8 +204,7 @@ public virtual void Lock(long position, long length) { throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum); } - - if (_strategy.IsClosed) + else if (_strategy.IsClosed) { ThrowHelper.ThrowObjectDisposedException_FileClosed(); } @@ -193,8 +219,7 @@ public virtual void Unlock(long position, long length) { throw new ArgumentOutOfRangeException(position < 0 ? nameof(position) : nameof(length), SR.ArgumentOutOfRange_NeedNonNegNum); } - - if (_strategy.IsClosed) + else if (_strategy.IsClosed) { ThrowHelper.ThrowObjectDisposedException_FileClosed(); } @@ -208,7 +233,7 @@ public override Task FlushAsync(CancellationToken cancellationToken) { return Task.FromCanceled(cancellationToken); } - if (_strategy.IsClosed) + else if (_strategy.IsClosed) { ThrowHelper.ThrowObjectDisposedException_FileClosed(); } @@ -230,10 +255,13 @@ public override Task ReadAsync(byte[] buffer, int offset, int count, Cancel ValidateBufferArguments(buffer, offset, count); if (cancellationToken.IsCancellationRequested) + { return Task.FromCanceled(cancellationToken); - - if (_strategy.IsClosed) + } + else if (_strategy.IsClosed) + { ThrowHelper.ThrowObjectDisposedException_FileClosed(); + } return _strategy.ReadAsync(buffer, offset, count, cancellationToken); } @@ -244,8 +272,7 @@ public override ValueTask ReadAsync(Memory buffer, CancellationToken { return ValueTask.FromCanceled(cancellationToken); } - - if (_strategy.IsClosed) + else if (_strategy.IsClosed) { ThrowHelper.ThrowObjectDisposedException_FileClosed(); } @@ -267,10 +294,13 @@ public override Task WriteAsync(byte[] buffer, int offset, int count, Cancellati ValidateBufferArguments(buffer, offset, count); if (cancellationToken.IsCancellationRequested) + { return Task.FromCanceled(cancellationToken); - - if (_strategy.IsClosed) + } + else if (_strategy.IsClosed) + { ThrowHelper.ThrowObjectDisposedException_FileClosed(); + } return _strategy.WriteAsync(buffer, offset, count, cancellationToken); } @@ -281,8 +311,7 @@ public override ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationTo { return ValueTask.FromCanceled(cancellationToken); } - - if (_strategy.IsClosed) + else if (_strategy.IsClosed) { ThrowHelper.ThrowObjectDisposedException_FileClosed(); } @@ -305,7 +334,10 @@ public override void Flush() /// public virtual void Flush(bool flushToDisk) { - if (_strategy.IsClosed) ThrowHelper.ThrowObjectDisposedException_FileClosed(); + if (_strategy.IsClosed) + { + ThrowHelper.ThrowObjectDisposedException_FileClosed(); + } _strategy.Flush(flushToDisk); } @@ -324,7 +356,9 @@ private void ValidateReadWriteArgs(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); if (_strategy.IsClosed) + { ThrowHelper.ThrowObjectDisposedException_FileClosed(); + } } /// Sets the length of this stream to the given value. @@ -332,13 +366,21 @@ private void ValidateReadWriteArgs(byte[] buffer, int offset, int count) public override void SetLength(long value) { if (value < 0) + { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); - if (_strategy.IsClosed) + } + else if (_strategy.IsClosed) + { ThrowHelper.ThrowObjectDisposedException_FileClosed(); - if (!CanSeek) + } + else if (!CanSeek) + { ThrowHelper.ThrowNotSupportedException_UnseekableStream(); - if (!CanWrite) + } + else if (!CanWrite) + { ThrowHelper.ThrowNotSupportedException_UnwritableStream(); + } _strategy.SetLength(value); } @@ -356,8 +398,15 @@ public override long Length { get { - if (_strategy.IsClosed) ThrowHelper.ThrowObjectDisposedException_FileClosed(); - if (!CanSeek) ThrowHelper.ThrowNotSupportedException_UnseekableStream(); + if (_strategy.IsClosed) + { + ThrowHelper.ThrowObjectDisposedException_FileClosed(); + } + else if (!CanSeek) + { + ThrowHelper.ThrowNotSupportedException_UnseekableStream(); + } + return _strategy.Length; } } @@ -368,17 +417,22 @@ public override long Position get { if (_strategy.IsClosed) + { ThrowHelper.ThrowObjectDisposedException_FileClosed(); - - if (!CanSeek) + } + else if (!CanSeek) + { ThrowHelper.ThrowNotSupportedException_UnseekableStream(); + } return _strategy.Position; } set { if (value < 0) + { ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.value, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } _strategy.Seek(value, SeekOrigin.Begin); } @@ -397,7 +451,7 @@ public override long Position /// The byte to write to the stream. public override void WriteByte(byte value) => _strategy.WriteByte(value); - protected override void Dispose(bool disposing) => _strategy?.DisposeInternal(disposing); + protected override void Dispose(bool disposing) => _strategy.DisposeInternal(disposing); internal void DisposeInternal(bool disposing) => Dispose(disposing); @@ -411,8 +465,15 @@ public override Task CopyToAsync(Stream destination, int bufferSize, Cancellatio public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { ValidateBufferArguments(buffer, offset, count); - if (_strategy.IsClosed) ThrowHelper.ThrowObjectDisposedException_FileClosed(); - if (!CanRead) ThrowHelper.ThrowNotSupportedException_UnreadableStream(); + + if (_strategy.IsClosed) + { + ThrowHelper.ThrowObjectDisposedException_FileClosed(); + } + else if (!CanRead) + { + ThrowHelper.ThrowNotSupportedException_UnreadableStream(); + } return _strategy.BeginRead(buffer, offset, count, callback, state); } @@ -420,7 +481,9 @@ public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, Asy public override int EndRead(IAsyncResult asyncResult) { if (asyncResult == null) + { throw new ArgumentNullException(nameof(asyncResult)); + } return _strategy.EndRead(asyncResult); } @@ -428,8 +491,15 @@ public override int EndRead(IAsyncResult asyncResult) public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state) { ValidateBufferArguments(buffer, offset, count); - if (_strategy.IsClosed) ThrowHelper.ThrowObjectDisposedException_FileClosed(); - if (!CanWrite) ThrowHelper.ThrowNotSupportedException_UnwritableStream(); + + if (_strategy.IsClosed) + { + ThrowHelper.ThrowObjectDisposedException_FileClosed(); + } + else if (!CanWrite) + { + ThrowHelper.ThrowNotSupportedException_UnwritableStream(); + } return _strategy.BeginWrite(buffer, offset, count, callback, state); } @@ -437,7 +507,9 @@ public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, As public override void EndWrite(IAsyncResult asyncResult) { if (asyncResult == null) + { throw new ArgumentNullException(nameof(asyncResult)); + } _strategy.EndWrite(asyncResult); } @@ -479,21 +551,5 @@ internal IAsyncResult BaseBeginWrite(byte[] buffer, int offset, int count, Async => base.BeginWrite(buffer, offset, count, callback, state); internal void BaseEndWrite(IAsyncResult asyncResult) => base.EndWrite(asyncResult); - - internal static bool IsIoRelatedException(Exception e) => - // These all derive from IOException - // DirectoryNotFoundException - // DriveNotFoundException - // EndOfStreamException - // FileLoadException - // FileNotFoundException - // PathTooLongException - // PipeException - e is IOException || - // Note that SecurityException is only thrown on runtimes that support CAS - // e is SecurityException || - e is UnauthorizedAccessException || - e is NotSupportedException || - (e is ArgumentException && !(e is ArgumentNullException)); } } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/AsyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/AsyncWindowsFileStreamStrategy.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs index 918d9afc88337..b02366ecfd13d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/AsyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { internal sealed partial class AsyncWindowsFileStreamStrategy : WindowsFileStreamStrategy, IFileStreamCompletionSourceStrategy { @@ -397,7 +397,7 @@ await FileStreamHelpers finally { // Make sure the stream's current position reflects where we ended up - if (!_fileHandle.IsClosed && CanSeek) + if (!_fileHandle.IsClosed && canSeek) { SeekCore(_fileHandle, 0, SeekOrigin.End); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/BufferedFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/BufferedFileStreamStrategy.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs index eb802eb646efb..b2a239b20b73b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/BufferedFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/BufferedFileStreamStrategy.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { // this type exists so we can avoid duplicating the buffering logic in every FileStreamStrategy implementation internal sealed class BufferedFileStreamStrategy : FileStreamStrategy @@ -23,7 +23,7 @@ internal sealed class BufferedFileStreamStrategy : FileStreamStrategy internal BufferedFileStreamStrategy(FileStreamStrategy strategy, int bufferSize) { - Debug.Assert(bufferSize > 1); + Debug.Assert(bufferSize > 1, "Buffering must not be enabled for smaller buffer sizes"); _strategy = strategy; _bufferSize = bufferSize; @@ -37,7 +37,7 @@ internal BufferedFileStreamStrategy(FileStreamStrategy strategy, int bufferSize) // so we enforce it by passing always true Dispose(true); } - catch (Exception e) when (FileStream.IsIoRelatedException(e)) + catch (Exception e) when (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. @@ -74,7 +74,7 @@ public override long Position { Debug.Assert(!(_writePos > 0 && _readPos != _readLen), "Read and Write buffers cannot both have data in them at the same time."); - return _strategy.Position + (_readPos - _readLen + _writePos); + return _strategy.Position + _readPos - _readLen + _writePos; } set { @@ -598,7 +598,7 @@ private void WriteByteSlow(byte value) ClearReadBufferBeforeWrite(); EnsureBufferAllocated(); } - else if (_writePos >= _bufferSize - 1) + else if (_writePos == _bufferSize - 1) { FlushWrite(); } @@ -1055,8 +1055,6 @@ private void EnsureCanWrite() private void EnsureBufferAllocated() { - Debug.Assert(_bufferSize > 0); - // BufferedFileStreamStrategy is not intended for multi-threaded use, so no worries about the get/set race on _buffer. if (_buffer == null) { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/DerivedFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/DerivedFileStreamStrategy.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/DerivedFileStreamStrategy.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/DerivedFileStreamStrategy.cs index e7dd824305f49..e6c63246d0a19 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/DerivedFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/DerivedFileStreamStrategy.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { // this type exists so we can avoid GetType() != typeof(FileStream) checks in FileStream // when FileStream was supposed to call base.Method() for such cases, we just call _fileStream.BaseMethod() diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamCompletionSource.Win32.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamCompletionSource.Win32.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStreamCompletionSource.Win32.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamCompletionSource.Win32.cs index edee6fce1315d..c23f04dc64b12 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamCompletionSource.Win32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamCompletionSource.Win32.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { // to avoid code duplicaiton of FileStreamCompletionSource for LegacyFileStreamStrategy and AsyncWindowsFileStreamStrategy // we have created the following interface that is a common contract for both of them diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs similarity index 93% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStreamHelpers.Unix.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 25a09f1ab8940..4d3639185feb4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -8,16 +8,16 @@ using System.Threading; using System.Threading.Tasks; -namespace System.IO +namespace System.IO.Strategies { // this type defines a set of stateless FileStream/FileStreamStrategy helper methods - internal static class FileStreamHelpers + internal static partial class FileStreamHelpers { // in the future we are most probably going to introduce more strategies (io_uring etc) - internal static FileStreamStrategy ChooseStrategy(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) + private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) => new LegacyFileStreamStrategy(handle, access, bufferSize, isAsync); - internal static FileStreamStrategy ChooseStrategy(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) => new LegacyFileStreamStrategy(path, mode, access, share, bufferSize, options); internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs similarity index 97% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStreamHelpers.Windows.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index 840d62cb03b80..f565d1f1e0f3b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamHelpers.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs @@ -9,21 +9,17 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { // this type defines a set of stateless FileStream/FileStreamStrategy helper methods - internal static class FileStreamHelpers + internal static partial class FileStreamHelpers { internal const int ERROR_BROKEN_PIPE = 109; internal const int ERROR_NO_DATA = 232; private const int ERROR_HANDLE_EOF = 38; private const int ERROR_IO_PENDING = 997; - // It's enabled by default. We are going to change that (by removing !) once we fix #16354, #25905 and #24847. - internal static bool UseLegacyStrategy { get; } - = !AppContextConfigHelper.GetBooleanConfig("System.IO.UseLegacyFileStream", "DOTNET_SYSTEM_IO_USELEGACYFILESTREAM"); - - internal static FileStreamStrategy ChooseStrategy(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) + private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) { if (UseLegacyStrategy) { @@ -37,7 +33,7 @@ internal static FileStreamStrategy ChooseStrategy(SafeFileHandle handle, FileAcc return EnableBufferingIfNeeded(strategy, bufferSize); } - internal static FileStreamStrategy ChooseStrategy(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) { if (UseLegacyStrategy) { @@ -146,7 +142,7 @@ internal static void VerifyHandleIsSync(SafeFileHandle handle) // If we can't check the handle, just assume it is ok. if (!(IsHandleSynchronous(handle, ignoreInvalid: false) ?? true)) - throw new ArgumentException(SR.Arg_HandleNotSync, nameof(handle)); + ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(handle)); } private static unsafe Interop.Kernel32.SECURITY_ATTRIBUTES GetSecAttrs(FileShare share) @@ -316,7 +312,7 @@ internal static void GetFileTypeSpecificInformation(SafeFileHandle handle, out b isPipe = handleType == Interop.Kernel32.FileTypes.FILE_TYPE_PIPE; } - internal static unsafe void SetLength(SafeFileHandle handle, string? path, long length) + internal static unsafe void SetFileLength(SafeFileHandle handle, string? path, long length) { var eofInfo = new Interop.Kernel32.FILE_END_OF_FILE_INFO { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs new file mode 100644 index 0000000000000..5eb9eabaeeb97 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Win32.SafeHandles; + +namespace System.IO.Strategies +{ + internal static partial class FileStreamHelpers + { + // It's enabled by default. We are going to change that once we fix #16354, #25905 and #24847. + internal static bool UseLegacyStrategy { get; } = GetLegacyFileStreamSetting(); + + private static bool GetLegacyFileStreamSetting() + { + if (AppContext.TryGetSwitch("System.IO.UseNet5CompatFileStream", out bool fileConfig)) + { + return fileConfig; + } + + string? envVar = Environment.GetEnvironmentVariable("DOTNET_SYSTEM_IO_USENET5COMPATFILESTREAM"); + return envVar is null + ? true // legacy is currently enabled by default; + : bool.IsTrueStringIgnoreCase(envVar) || envVar.Equals("1"); + } + + internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, SafeFileHandle handle, FileAccess access, int bufferSize, bool isAsync) + => WrapIfDerivedType(fileStream, ChooseStrategyCore(handle, access, bufferSize, isAsync)); + + internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + => WrapIfDerivedType(fileStream, ChooseStrategyCore(path, mode, access, share, bufferSize, options)); + + private static FileStreamStrategy WrapIfDerivedType(FileStream fileStream, FileStreamStrategy strategy) + => fileStream.GetType() == typeof(FileStream) + ? strategy + : new DerivedFileStreamStrategy(fileStream, strategy); + + internal static bool IsIoRelatedException(Exception e) => + // These all derive from IOException + // DirectoryNotFoundException + // DriveNotFoundException + // EndOfStreamException + // FileLoadException + // FileNotFoundException + // PathTooLongException + // PipeException + e is IOException || + // Note that SecurityException is only thrown on runtimes that support CAS + // e is SecurityException || + e is UnauthorizedAccessException || + e is NotSupportedException || + (e is ArgumentException && !(e is ArgumentNullException)); + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamStrategy.cs similarity index 96% rename from src/libraries/System.Private.CoreLib/src/System/IO/FileStreamStrategy.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamStrategy.cs index 416e48a01eac0..fdeba8f0df233 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamStrategy.cs @@ -3,7 +3,7 @@ using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { internal abstract class FileStreamStrategy : Stream { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Lock.OSX.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Lock.OSX.cs similarity index 95% rename from src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Lock.OSX.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Lock.OSX.cs index 599b51694d179..5d00486b26b02 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Lock.OSX.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Lock.OSX.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace System.IO +namespace System.IO.Strategies { internal sealed partial class LegacyFileStreamStrategy : FileStreamStrategy { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Lock.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Lock.Unix.cs similarity index 97% rename from src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Lock.Unix.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Lock.Unix.cs index 5233dcdb7087f..d6a534ffb2595 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Lock.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Lock.Unix.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace System.IO +namespace System.IO.Strategies { internal sealed partial class LegacyFileStreamStrategy : FileStreamStrategy { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Unix.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Unix.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Unix.cs index 26818014bbbcf..a9614b1d55386 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Unix.cs @@ -8,7 +8,7 @@ using System.Threading; using System.Threading.Tasks; -namespace System.IO +namespace System.IO.Strategies { /// Provides an implementation of a file stream for Unix files. internal sealed partial class LegacyFileStreamStrategy : FileStreamStrategy @@ -172,7 +172,7 @@ protected override void Dispose(bool disposing) { FlushWriteBuffer(); } - catch (Exception e) when (!disposing && FileStream.IsIoRelatedException(e)) + 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. diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Windows.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Windows.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Windows.cs index ab8cc977a5e05..8890bf3053474 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.Windows.cs @@ -34,7 +34,7 @@ * */ -namespace System.IO +namespace System.IO.Strategies { internal sealed partial class LegacyFileStreamStrategy : FileStreamStrategy, IFileStreamCompletionSourceStrategy { @@ -192,7 +192,7 @@ protected override void Dispose(bool disposing) { FlushWriteBuffer(!disposing); } - catch (Exception e) when (!disposing && FileStream.IsIoRelatedException(e)) + 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. @@ -328,7 +328,7 @@ private unsafe void SetLengthCore(long value) Debug.Assert(value >= 0, "value >= 0"); VerifyOSHandlePosition(); - FileStreamHelpers.SetLength(_fileHandle, _path, value); + FileStreamHelpers.SetFileLength(_fileHandle, _path, value); if (_filePosition > value) { @@ -434,7 +434,7 @@ private unsafe int ReadNative(Span buffer) else { if (errorCode == ERROR_INVALID_PARAMETER) - throw new ArgumentException(SR.Arg_HandleNotSync, "_fileHandle"); + ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(_fileHandle)); throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.cs index 5d90aecac6ac7..6d06e9614056e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/LegacyFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/LegacyFileStreamStrategy.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { // This type is partial so we can avoid code duplication between Windows and Unix Legacy implementations internal sealed partial class LegacyFileStreamStrategy : FileStreamStrategy diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/SyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs similarity index 97% rename from src/libraries/System.Private.CoreLib/src/System/IO/SyncWindowsFileStreamStrategy.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs index 30d01134481cb..bcd9c67e7f8ae 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/SyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.Win32.SafeHandles; -namespace System.IO +namespace System.IO.Strategies { internal sealed class SyncWindowsFileStreamStrategy : WindowsFileStreamStrategy { @@ -33,7 +33,7 @@ protected override void OnInitFromHandle(SafeFileHandle handle) // If we can't check the handle, just assume it is ok. if (!(FileStreamHelpers.IsHandleSynchronous(handle, ignoreInvalid: false) ?? true)) - throw new ArgumentException(SR.Arg_HandleNotSync, nameof(handle)); + ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(handle)); } public override int Read(byte[] buffer, int offset, int count) => ReadSpan(new Span(buffer, offset, count)); @@ -119,7 +119,7 @@ private unsafe int ReadSpan(Span destination) else { if (errorCode == ERROR_INVALID_PARAMETER) - throw new ArgumentException(SR.Arg_HandleNotSync, "_fileHandle"); + ThrowHelper.ThrowArgumentException_HandleNotSync(nameof(_fileHandle)); throw Win32Marshal.GetExceptionForWin32Error(errorCode, _path); } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/WindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs similarity index 99% rename from src/libraries/System.Private.CoreLib/src/System/IO/WindowsFileStreamStrategy.cs rename to src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs index 45917d0bc6d2c..d238bc23e4c4e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/WindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs @@ -6,7 +6,7 @@ using Microsoft.Win32.SafeHandles; using System.Runtime.CompilerServices; -namespace System.IO +namespace System.IO.Strategies { // this type serves some basic functionality that is common for Async and Sync Windows File Stream Strategies internal abstract class WindowsFileStreamStrategy : FileStreamStrategy @@ -263,7 +263,7 @@ protected unsafe void SetLengthCore(long value) Debug.Assert(value >= 0, "value >= 0"); VerifyOSHandlePosition(); - FileStreamHelpers.SetLength(_fileHandle, _path, value); + FileStreamHelpers.SetFileLength(_fileHandle, _path, value); if (_filePosition > value) { diff --git a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs index 27c29017c3846..d49e39d8b2f92 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ThrowHelper.cs @@ -219,6 +219,12 @@ internal static void ThrowArgumentException(ExceptionResource resource, Exceptio throw GetArgumentException(resource, argument); } + [DoesNotReturn] + internal static void ThrowArgumentException_HandleNotSync(string paramName) + { + throw new ArgumentException(SR.Arg_HandleNotSync, paramName); + } + [DoesNotReturn] internal static void ThrowArgumentNullException(ExceptionArgument argument) {