diff --git a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs index e2f518e409dca0..bdc85f848e9167 100644 --- a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs +++ b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Diagnostics; +using System.IO; using System.Runtime.InteropServices; internal static partial class Interop @@ -11,30 +13,31 @@ internal static partial class NtDll // https://msdn.microsoft.com/en-us/library/bb432380.aspx // https://msdn.microsoft.com/en-us/library/windows/hardware/ff566424.aspx [DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)] - private static extern unsafe int NtCreateFile( + private static extern unsafe uint NtCreateFile( out IntPtr FileHandle, DesiredAccess DesiredAccess, ref OBJECT_ATTRIBUTES ObjectAttributes, out IO_STATUS_BLOCK IoStatusBlock, long* AllocationSize, - System.IO.FileAttributes FileAttributes, - System.IO.FileShare ShareAccess, + FileAttributes FileAttributes, + FileShare ShareAccess, CreateDisposition CreateDisposition, CreateOptions CreateOptions, void* EaBuffer, uint EaLength); - internal static unsafe (int status, IntPtr handle) CreateFile( + internal static unsafe (uint status, IntPtr handle) CreateFile( ReadOnlySpan path, IntPtr rootDirectory, CreateDisposition createDisposition, DesiredAccess desiredAccess = DesiredAccess.FILE_GENERIC_READ | DesiredAccess.SYNCHRONIZE, - System.IO.FileShare shareAccess = System.IO.FileShare.ReadWrite | System.IO.FileShare.Delete, - System.IO.FileAttributes fileAttributes = 0, + FileShare shareAccess = FileShare.ReadWrite | FileShare.Delete, + FileAttributes fileAttributes = 0, CreateOptions createOptions = CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT, ObjectAttributes objectAttributes = ObjectAttributes.OBJ_CASE_INSENSITIVE, void* eaBuffer = null, - uint eaLength = 0) + uint eaLength = 0, + long* allocationSize = null) { fixed (char* c = &MemoryMarshal.GetReference(path)) { @@ -50,12 +53,12 @@ internal static unsafe (int status, IntPtr handle) CreateFile( objectAttributes, rootDirectory); - int status = NtCreateFile( + uint status = NtCreateFile( out IntPtr handle, desiredAccess, ref attributes, out IO_STATUS_BLOCK statusBlock, - AllocationSize: null, + AllocationSize: allocationSize, fileAttributes, shareAccess, createDisposition, @@ -67,6 +70,107 @@ internal static unsafe (int status, IntPtr handle) CreateFile( } } + internal static unsafe (uint status, IntPtr handle) CreateFile(ReadOnlySpan path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) + => CreateFile( + path: path, + rootDirectory: IntPtr.Zero, + createDisposition: GetCreateDisposition(mode), + desiredAccess: GetDesiredAccess(access, mode, options), + shareAccess: GetShareAccess(share), + fileAttributes: GetFileAttributes(options), + createOptions: GetCreateOptions(options), + objectAttributes: GetObjectAttributes(share), + allocationSize: &allocationSize); + + private static CreateDisposition GetCreateDisposition(FileMode mode) + { + switch (mode) + { + case FileMode.CreateNew: + return CreateDisposition.FILE_CREATE; + case FileMode.Create: + return CreateDisposition.FILE_SUPERSEDE; + case FileMode.OpenOrCreate: + case FileMode.Append: // has extra handling in GetDesiredAccess + return CreateDisposition.FILE_OPEN_IF; + case FileMode.Truncate: + return CreateDisposition.FILE_OVERWRITE; + default: + Debug.Assert(mode == FileMode.Open); // the enum value is validated in FileStream ctor + return CreateDisposition.FILE_OPEN; + } + } + + private static DesiredAccess GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options) + { + DesiredAccess result = 0; + + if ((access & FileAccess.Read) != 0) + { + result |= DesiredAccess.FILE_GENERIC_READ; + } + if ((access & FileAccess.Write) != 0) + { + result |= DesiredAccess.FILE_GENERIC_WRITE; + } + if (fileMode == FileMode.Append) + { + result |= DesiredAccess.FILE_APPEND_DATA; + } + if ((options & FileOptions.Asynchronous) == 0) + { + result |= DesiredAccess.SYNCHRONIZE; // requried by FILE_SYNCHRONOUS_IO_NONALERT + } + + return result; + } + + private static FileShare GetShareAccess(FileShare share) + => share & ~FileShare.Inheritable; // FileShare.Inheritable is handled in GetObjectAttributes + + private static FileAttributes GetFileAttributes(FileOptions options) + => (options & FileOptions.Encrypted) != 0 ? FileAttributes.Encrypted : 0; + + // FileOptions.Encrypted is handled in GetFileAttributes + private static CreateOptions GetCreateOptions(FileOptions options) + { + // Every directory is just a directory FILE. + // FileStream does not allow for opening directories on purpose. + // FILE_NON_DIRECTORY_FILE is used to ensure that + CreateOptions result = CreateOptions.FILE_NON_DIRECTORY_FILE; + + if ((options & FileOptions.WriteThrough) != 0) + { + result |= CreateOptions.FILE_WRITE_THROUGH; + } + if ((options & FileOptions.RandomAccess) != 0) + { + result |= CreateOptions.FILE_RANDOM_ACCESS; + } + if ((options & FileOptions.SequentialScan) != 0) + { + result |= CreateOptions.FILE_SEQUENTIAL_ONLY; + } + if ((options & FileOptions.DeleteOnClose) != 0) + { + result |= CreateOptions.FILE_DELETE_ON_CLOSE; + } + if ((options & FileOptions.Asynchronous) == 0) + { + // it's async by default, so we need to disable it when async was not requested + result |= CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT; // has extra handling in GetDesiredAccess + } + if (((int)options & 0x20000000) != 0) // NoBuffering + { + result |= CreateOptions.FILE_NO_INTERMEDIATE_BUFFERING; + } + + return result; + } + + private static ObjectAttributes GetObjectAttributes(FileShare share) + => (share & FileShare.Inheritable) != 0 ? ObjectAttributes.OBJ_INHERIT : 0; + /// /// File creation disposition when calling directly to NT APIs. /// diff --git a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs index cad1a12c5a485b..db66f2c939c84a 100644 --- a/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs +++ b/src/libraries/System.IO.Compression.ZipFile/src/System/IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs @@ -75,7 +75,7 @@ public static void ExtractToFile(this ZipArchiveEntry source, string destination // Rely on FileStream's ctor for further checking destinationFileName parameter FileMode fMode = overwrite ? FileMode.Create : FileMode.CreateNew; - using (Stream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, useAsync: false)) + using (Stream fs = new FileStream(destinationFileName, fMode, FileAccess.Write, FileShare.None, bufferSize: 0x1000, allocationSize: source.Length)) { using (Stream es = source.Open()) es.CopyTo(fs); diff --git a/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs b/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs index 44c8cec1317d26..22e990194f5d88 100644 --- a/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs +++ b/src/libraries/System.IO.FileSystem/src/System/IO/Enumeration/FileSystemEnumerator.Win32.cs @@ -60,7 +60,7 @@ private unsafe bool GetData() private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan relativePath, string fullPath) { - (int status, IntPtr handle) = Interop.NtDll.CreateFile( + (uint status, IntPtr handle) = Interop.NtDll.CreateFile( relativePath, _directoryHandle, Interop.NtDll.CreateDisposition.FILE_OPEN, @@ -68,7 +68,7 @@ private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan relativeP createOptions: Interop.NtDll.CreateOptions.FILE_SYNCHRONOUS_IO_NONALERT | Interop.NtDll.CreateOptions.FILE_DIRECTORY_FILE | Interop.NtDll.CreateOptions.FILE_OPEN_FOR_BACKUP_INTENT); - switch ((uint)status) + switch (status) { case Interop.StatusOptions.STATUS_SUCCESS: return handle; @@ -77,7 +77,7 @@ private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan relativeP // such as ERROR_ACCESS_DENIED. As we want to replicate Win32 handling/reporting and the mapping isn't documented, // we should always do our logic on the converted code, not the NTSTATUS. - int error = (int)Interop.NtDll.RtlNtStatusToDosError(status); + int error = (int)Interop.NtDll.RtlNtStatusToDosError((int)status); if (ContinueOnDirectoryError(error, ignoreNotFound: true)) { diff --git a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs index f086c2cc23609f..1e952ac94005ee 100644 --- a/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs +++ b/src/libraries/System.IO.FileSystem/tests/FileStream/FileStreamConformanceTests.cs @@ -14,6 +14,7 @@ public abstract class FileStreamStandaloneConformanceTests : StandaloneStreamCon { protected abstract FileOptions Options { get; } protected abstract int BufferSize { get; } + protected abstract long AllocationSize { get; } private Task CreateStream(byte[] initialData, FileAccess access) { @@ -23,7 +24,7 @@ private Task CreateStream(byte[] initialData, FileAccess access) File.WriteAllBytes(path, initialData); } - return Task.FromResult(new FileStream(path, FileMode.OpenOrCreate, access, FileShare.None, BufferSize, Options)); + return Task.FromResult(new FileStream(path, FileMode.OpenOrCreate, access, FileShare.None, BufferSize, Options, AllocationSize)); } protected override Task CreateReadOnlyStreamCore(byte[] initialData) => CreateStream(initialData, FileAccess.Read); @@ -188,16 +189,47 @@ public async Task WriteByteFlushesTheBufferWhenItBecomesFull() } } + [Fact] + public void WhenFileStreamFailsToPreallocateDiskSpaceTheErrorMessageContainsAllTheDetails() + { + const long tooMuch = 1024L * 1024L * 1024L * 1024L; // 1 TB + + string filePath = GetTestFilePath(); + IOException ex = Assert.Throws(() => new FileStream(filePath, FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, BufferSize, Options, tooMuch)); + Assert.Contains("disk was full", ex.Message); + Assert.Contains(filePath, ex.Message); + Assert.Contains(AllocationSize.ToString(), ex.Message); + } + } + public class UnbufferedSyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests { protected override FileOptions Options => FileOptions.None; protected override int BufferSize => 1; + protected override long AllocationSize => 0; + } + + public class UnbufferedPreallocatedSyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests + { + protected override FileOptions Options => FileOptions.None; + protected override int BufferSize => 1; + + // any AllocationSize > 0 executes the code path where we try to pre-allocate the disk space + protected override long AllocationSize => 1; } public class BufferedSyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests { protected override FileOptions Options => FileOptions.None; protected override int BufferSize => 10; + protected override long AllocationSize => 0; + } + + public class BufferedPreallocatedSyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests + { + protected override FileOptions Options => FileOptions.None; + protected override int BufferSize => 10; + protected override long AllocationSize => 1; } [ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] @@ -206,6 +238,16 @@ public class UnbufferedAsyncFileStreamStandaloneConformanceTests : FileStreamSta { protected override FileOptions Options => FileOptions.Asynchronous; protected override int BufferSize => 1; + protected override long AllocationSize => 0; + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] + [PlatformSpecific(~TestPlatforms.Browser)] // copied from base class due to https://github.com/xunit/xunit/issues/2186 + public class UnbufferedPreallocatedAsyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests + { + protected override FileOptions Options => FileOptions.Asynchronous; + protected override int BufferSize => 1; + protected override long AllocationSize => 1; } [ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] @@ -214,6 +256,16 @@ public class BufferedAsyncFileStreamStandaloneConformanceTests : FileStreamStand { protected override FileOptions Options => FileOptions.Asynchronous; protected override int BufferSize => 10; + protected override long AllocationSize => 0; + } + + [ActiveIssue("https://github.com/dotnet/runtime/issues/34583", TestPlatforms.Windows, TargetFrameworkMonikers.Netcoreapp, TestRuntimes.Mono)] + [PlatformSpecific(~TestPlatforms.Browser)] // copied from base class due to https://github.com/xunit/xunit/issues/2186 + public class BufferedPreallocatedAsyncFileStreamStandaloneConformanceTests : FileStreamStandaloneConformanceTests + { + protected override FileOptions Options => FileOptions.Asynchronous; + protected override int BufferSize => 10; + protected override long AllocationSize => 1; } public class AnonymousPipeFileStreamConnectedConformanceTests : ConnectedStreamConformanceTests diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index ab45140cc875f7..dc28d0991893f0 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -2644,6 +2644,9 @@ Cannot create '{0}' because a file or directory with the same name already exists. + + Failed to create '{0}' with allocation size '{1}' because the disk was full. + BindHandle for ThreadPool failed on this handle. 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 a2bb38f63185c0..6a71434a4fda99 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 @@ -1348,6 +1348,9 @@ Common\Interop\Windows\Kernel32\Interop.CreateFile.cs + + Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs + Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs @@ -1513,6 +1516,12 @@ Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs + + Common\Interop\Windows\Interop.UNICODE_STRING.cs + + + Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs + Common\Interop\Windows\Kernel32\Interop.SecurityOptions.cs 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 eb9d98750f758d..d1eaf1f11f3cbb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs @@ -130,6 +130,41 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share } public FileStream(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + : this(path, mode, access, share, bufferSize, options, 0) + { + } + + /// + /// Initializes a new instance of the class with the specified path, creation mode, read/write and sharing permission, the access other FileStreams can have to the same file, the buffer size, additional file options and the allocation size. + /// + /// A relative or absolute path for the file that the current object will encapsulate. + /// One of the enumeration values that determines how to open or create the file. + /// A bitwise combination of the enumeration values that determines how the file can be accessed by the object. This also determines the values returned by the and properties of the object. is if specifies a disk file. + /// A bitwise combination of the enumeration values that determines how the file will be shared by processes. The default value is Read. + /// A positive value greater than 0 indicating the buffer size. The default buffer size is 4096. + /// A bitwise combination of the enumeration values that specifies additional file options. The default value is None which means synchronous IO. + /// The initial allocation size in bytes for the file. A nonzero value has no effect unless the file is being created, overwritten, or superseded. + /// is . + /// is an empty string (""), contains only white space, or contains one or more invalid characters. + /// -or- + /// refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in an NTFS environment. + /// refers to a non-file device, such as "con:", "com1:", "lpt1:", etc. in a non-NTFS environment. + /// is negative or zero. + /// -or- + /// , , or contain an invalid value. + /// The file cannot be found, such as when is or , and the file specified by does not exist. The file must already exist in these modes. + /// An I/O error, such as specifying when the file specified by already exists, occurred. + /// -or- + /// The stream has been closed. + /// -or- + /// The disk was full. + /// The caller does not have the required permission. + /// The specified path is invalid, such as being on an unmapped drive. + /// The requested is not permitted by the operating system for the specified , such as when is or and the file or directory is set for read-only access. + /// -or- + /// is specified for , but file encryption is not supported on the current platform. + /// The specified path, file name, or both exceed the system-defined maximum length. + public FileStream(string path, FileMode mode, FileAccess access, FileShare share = DefaultShare, int bufferSize = DefaultBufferSize, FileOptions options = FileOptions.None, long allocationSize = 0) { if (path == null) { @@ -191,7 +226,7 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share SerializationInfo.ThrowIfDeserializationInProgress("AllowFileWrites", ref s_cachedSerializationSwitch); } - _strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options); + _strategy = FileStreamHelpers.ChooseStrategy(this, path, mode, access, share, bufferSize, options, allocationSize); } [Obsolete("This property has been deprecated. Please use FileStream's SafeFileHandle property instead. https://go.microsoft.com/fwlink/?linkid=14202")] diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs index b7f24351a0bda4..e7925787bd8270 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/AsyncWindowsFileStreamStrategy.cs @@ -18,8 +18,8 @@ internal AsyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access { } - internal AsyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) - : base(path, mode, access, share, options) + internal AsyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) + : base(path, mode, access, share, options, allocationSize) { } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs index 0d2c5df52be3aa..418dadc99378fd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Unix.cs @@ -17,10 +17,10 @@ internal static partial class FileStreamHelpers private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync) => new Net5CompatFileStreamStrategy(handle, access, bufferSize, isAsync); - private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) - => new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options); + private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long allocationSize) + => new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options, allocationSize); - internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) + internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) { // Translate the arguments into arguments for an open call. Interop.Sys.OpenFlags openFlags = PreOpenConfigurationFromOptions(mode, access, share, options); diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index fd3218f6eb4c3e..5c2271a8bf9031 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs @@ -18,6 +18,7 @@ internal static partial class FileStreamHelpers internal const int ERROR_NO_DATA = 232; private const int ERROR_HANDLE_EOF = 38; private const int ERROR_IO_PENDING = 997; + private const uint ERROR_STATUS_DISK_FULL = 0xC000007F; private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync) { @@ -33,58 +34,74 @@ private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, File return EnableBufferingIfNeeded(strategy, bufferSize); } - private static FileStreamStrategy ChooseStrategyCore(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, long allocationSize) { if (UseNet5CompatStrategy) { - return new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options); + return new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options, allocationSize); } WindowsFileStreamStrategy strategy = (options & FileOptions.Asynchronous) != 0 - ? new AsyncWindowsFileStreamStrategy(path, mode, access, share, options) - : new SyncWindowsFileStreamStrategy(path, mode, access, share, options); + ? new AsyncWindowsFileStreamStrategy(path, mode, access, share, options, allocationSize) + : new SyncWindowsFileStreamStrategy(path, mode, access, share, options, allocationSize); return EnableBufferingIfNeeded(strategy, bufferSize); } - // TODO: we might want to consider strategy.IsPipe here and never enable buffering for async pipes internal static FileStreamStrategy EnableBufferingIfNeeded(WindowsFileStreamStrategy strategy, int bufferSize) => bufferSize == 1 ? strategy : new BufferedFileStreamStrategy(strategy, bufferSize); - internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) - => CreateFileOpenHandle(path, mode, access, share, options); + internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) + => CreateFileOpenHandle(path, mode, access, share, options, allocationSize); - private static unsafe SafeFileHandle CreateFileOpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) + private static unsafe SafeFileHandle CreateFileOpenHandle(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) { - Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); + using (DisableMediaInsertionPrompt.Create()) + { + Debug.Assert(path != null); - int fAccess = - ((access & FileAccess.Read) == FileAccess.Read ? Interop.Kernel32.GenericOperations.GENERIC_READ : 0) | - ((access & FileAccess.Write) == FileAccess.Write ? Interop.Kernel32.GenericOperations.GENERIC_WRITE : 0); + if (allocationSize > 0) + { + string prefixedAbsolutePath = PathInternal.IsExtended(path) ? path : @"\??\" + Path.GetFullPath(path); // TODO: we might consider getting rid of this managed allocation, + (uint ntStatus, IntPtr fileHandle) = Interop.NtDll.CreateFile(prefixedAbsolutePath, mode, access, share, options, allocationSize); + if (ntStatus == 0) + { + return ValidateFileHandle(new SafeFileHandle(fileHandle, ownsHandle: true), path, (options & FileOptions.Asynchronous) != 0); + } + else if (ntStatus == ERROR_STATUS_DISK_FULL) + { + throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, path, allocationSize)); + } - // Our Inheritable bit was stolen from Windows, but should be set in - // the security attributes class. Don't leave this bit set. - share &= ~FileShare.Inheritable; + // NtCreateFile has failed for some other reason than a full disk. + // Instead of implementing the mapping for every NS Status value (there are plenty of them: https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55) + // or using RtlNtStatusToDosError & GetExceptionForWin32Error + // the code falls back to CreateFileW that just throws the right exception. + } - // Must use a valid Win32 constant here... - if (mode == FileMode.Append) - mode = FileMode.OpenOrCreate; + Interop.Kernel32.SECURITY_ATTRIBUTES secAttrs = GetSecAttrs(share); - int flagsAndAttributes = (int)options; + int fAccess = + ((access & FileAccess.Read) == FileAccess.Read ? Interop.Kernel32.GenericOperations.GENERIC_READ : 0) | + ((access & FileAccess.Write) == FileAccess.Write ? Interop.Kernel32.GenericOperations.GENERIC_WRITE : 0); - // For mitigating local elevation of privilege attack through named pipes - // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the - // named pipe server can't impersonate a high privileged client security context - // (note that this is the effective default on CreateFile2) - flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS); + // Our Inheritable bit was stolen from Windows, but should be set in + // the security attributes class. Don't leave this bit set. + share &= ~FileShare.Inheritable; - using (DisableMediaInsertionPrompt.Create()) - { - Debug.Assert(path != null); - return ValidateFileHandle( - Interop.Kernel32.CreateFile(path, fAccess, share, &secAttrs, mode, flagsAndAttributes, IntPtr.Zero), - path, - (options & FileOptions.Asynchronous) != 0); + // Must use a valid Win32 constant here... + if (mode == FileMode.Append) + mode = FileMode.OpenOrCreate; + + int flagsAndAttributes = (int)options; + + // For mitigating local elevation of privilege attack through named pipes + // make sure we always call CreateFile with SECURITY_ANONYMOUS so that the + // named pipe server can't impersonate a high privileged client security context + // (note that this is the effective default on CreateFile2) + flagsAndAttributes |= (Interop.Kernel32.SecurityOptions.SECURITY_SQOS_PRESENT | Interop.Kernel32.SecurityOptions.SECURITY_ANONYMOUS); + + return ValidateFileHandle(Interop.Kernel32.CreateFile(path, fAccess, share, &secAttrs, mode, flagsAndAttributes, IntPtr.Zero), path, (options & FileOptions.Asynchronous) != 0); } } 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 index 1ca2e563998caa..65e8c35929527f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.cs @@ -12,8 +12,8 @@ internal static partial class FileStreamHelpers internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, SafeFileHandle handle, FileAccess access, FileShare share, int bufferSize, bool isAsync) => WrapIfDerivedType(fileStream, ChooseStrategyCore(handle, access, share, 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)); + internal static FileStreamStrategy ChooseStrategy(FileStream fileStream, string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long allocationSize) + => WrapIfDerivedType(fileStream, ChooseStrategyCore(path, mode, access, share, bufferSize, options, allocationSize)); private static FileStreamStrategy WrapIfDerivedType(FileStream fileStream, FileStreamStrategy strategy) => fileStream.GetType() == typeof(FileStream) diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs index 03ca3533e717b7..58105ad890f294 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/Net5CompatFileStreamStrategy.cs @@ -78,7 +78,7 @@ internal Net5CompatFileStreamStrategy(SafeFileHandle handle, FileAccess access, _fileHandle = handle; } - internal Net5CompatFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) + internal Net5CompatFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options, long allocationSize) { string fullPath = Path.GetFullPath(path); @@ -89,7 +89,7 @@ internal Net5CompatFileStreamStrategy(string path, FileMode mode, FileAccess acc if ((options & FileOptions.Asynchronous) != 0) _useAsyncIO = true; - _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options); + _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options, allocationSize); try { diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs index 3639b4b5fb4daf..6b6546d91aed75 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/SyncWindowsFileStreamStrategy.cs @@ -15,8 +15,8 @@ internal SyncWindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access, { } - internal SyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) - : base(path, mode, access, share, options) + internal SyncWindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) + : base(path, mode, access, share, options, allocationSize) { } diff --git a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs index d42494c19086d2..0d77839e8ca13d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/WindowsFileStreamStrategy.cs @@ -44,7 +44,7 @@ internal WindowsFileStreamStrategy(SafeFileHandle handle, FileAccess access, Fil _fileHandle = handle; } - internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options) + internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) { string fullPath = Path.GetFullPath(path); @@ -52,7 +52,7 @@ internal WindowsFileStreamStrategy(string path, FileMode mode, FileAccess access _access = access; _share = share; - _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options); + _fileHandle = FileStreamHelpers.OpenHandle(fullPath, mode, access, share, options, allocationSize); try { diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 4c9670bea66964..f9fca36cd65e51 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -7296,6 +7296,7 @@ public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess acc public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, int bufferSize) { } public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, int bufferSize, bool useAsync) { } public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share, int bufferSize, System.IO.FileOptions options) { } + public FileStream(string path, System.IO.FileMode mode, System.IO.FileAccess access, System.IO.FileShare share = System.IO.FileShare.Read, int bufferSize = 4096, System.IO.FileOptions options = System.IO.FileOptions.None, long allocationSize = 0) { } public override bool CanRead { get { throw null; } } public override bool CanSeek { get { throw null; } } public override bool CanWrite { get { throw null; } }