From 5ba70507400091ff7e4b52933bba7c268171aafd Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 12 Apr 2021 13:22:32 +0200 Subject: [PATCH 1/8] add the new ctor to the ref assembly --- src/libraries/System.Runtime/ref/System.Runtime.cs | 1 + 1 file changed, 1 insertion(+) 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; } } From 0352d3cb09e276b0c62be9804952201d409476cf Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 12 Apr 2021 13:22:56 +0200 Subject: [PATCH 2/8] add tests --- .../FileStream/FileStreamConformanceTests.cs | 54 ++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) 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 From 83fbf8daf932b1aa7ce6ae8097355d1711c422d2 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 12 Apr 2021 13:41:22 +0200 Subject: [PATCH 3/8] add new ctor --- .../src/System/IO/FileStream.cs | 7 ++++++- .../Strategies/AsyncWindowsFileStreamStrategy.cs | 4 ++-- .../IO/Strategies/FileStreamHelpers.Unix.cs | 2 +- .../IO/Strategies/FileStreamHelpers.Windows.cs | 16 ++++++++-------- .../System/IO/Strategies/FileStreamHelpers.cs | 4 ++-- .../Strategies/Net5CompatFileStreamStrategy.cs | 4 ++-- .../Strategies/SyncWindowsFileStreamStrategy.cs | 4 ++-- .../IO/Strategies/WindowsFileStreamStrategy.cs | 4 ++-- 8 files changed, 25 insertions(+), 20 deletions(-) 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..614775610412a5 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,11 @@ 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) + { + } + + 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 +196,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..64522ea320fee6 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 @@ -20,7 +20,7 @@ private static FileStreamStrategy ChooseStrategyCore(SafeFileHandle handle, File private static FileStreamStrategy ChooseStrategyCore(string path, FileMode mode, FileAccess access, FileShare share, int bufferSize, FileOptions options) => new Net5CompatFileStreamStrategy(path, mode, access, share, bufferSize, options); - 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..4e5543d7af9e3f 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,28 +34,27 @@ 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); 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 { From e9b9774bcdafc32025b486c7733eeec01e8710b7 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 12 Apr 2021 14:12:01 +0200 Subject: [PATCH 4/8] initial Windows implementation --- .../Windows/Kernel32/Interop.CreateFile.cs | 173 ++++++++++++++++++ .../Kernel32/Interop.IO_STATUS_BLOCK.cs | 18 ++ .../Kernel32/Interop.OBJECT_ATTRIBUTES.cs | 33 ++++ .../Kernel32/Interop.UNICODE_STRING.cs | 27 +++ .../src/Resources/Strings.resx | 3 + .../System.Private.CoreLib.Shared.projitems | 9 + .../src/System/IO/FileStream.cs | 30 +++ .../Strategies/FileStreamHelpers.Windows.cs | 62 ++++--- 8 files changed, 332 insertions(+), 23 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs create mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs create mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs index ce5bfd9fd5321f..6e88ab929e1108 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs @@ -3,6 +3,7 @@ using Microsoft.Win32.SafeHandles; using System; +using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -23,6 +24,22 @@ private static extern unsafe SafeFileHandle CreateFilePrivate( int dwFlagsAndAttributes, IntPtr hTemplateFile); + // https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntcreatefile + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntcreatefile + [DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)] + private static unsafe extern uint NtCreateFile( + out IntPtr fileHandle, + int desiredAccess, + ref OBJECT_ATTRIBUTES objectAttributes, + out IO_STATUS_BLOCK ioStatusBlock, + long* allocationSize, + uint fileAttributes, + uint shareAccess, + uint createDisposition, + uint createOptions, + void* extendedAttributesBuffer, + uint extendedAttributesLength); + internal static unsafe SafeFileHandle CreateFile( string lpFileName, int dwDesiredAccess, @@ -46,5 +63,161 @@ internal static unsafe SafeFileHandle CreateFile( lpFileName = PathInternal.EnsureExtendedPrefixIfNeeded(lpFileName); return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, IntPtr.Zero); } + + internal static unsafe uint NtCreateFile(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize, out IntPtr fileHandle) + { + string prefixedAbsolutePath = PathInternal.IsExtended(path) + ? path + : @"\??\" + Path.GetFullPath(path); // TODO: we might consider getting rid of this managed allocation + + fixed (char* filePath = prefixedAbsolutePath) + { + UNICODE_STRING unicodeString = new UNICODE_STRING(filePath, prefixedAbsolutePath.Length); + OBJECT_ATTRIBUTES objectAttributes = new OBJECT_ATTRIBUTES(&unicodeString, GetObjectAttributes(share)); + + return NtCreateFile( + fileHandle: out fileHandle, + desiredAccess: GetDesiredAccess(access, mode, options), + objectAttributes: ref objectAttributes, + ioStatusBlock: out _, + allocationSize: &allocationSize, + fileAttributes: GetFileAttributes(options), + shareAccess: GetShareAccess(share), + createDisposition: GetCreateDisposition(mode), + createOptions: GetCreateOptions(options), + extendedAttributesBuffer: default, + extendedAttributesLength: default); + } + } + + private static uint GetObjectAttributes(FileShare share) + { + uint result = 0;// 0x00000040; // Lookups for this object should be case insensitive. [OBJ_CASE_INSENSITIVE] + + if ((share & FileShare.Inheritable) != 0 ) + { + result |= 0x00000002; + } + + return result; + } + + private static int GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options) + { + int result = 0; + + if ((access & FileAccess.Read) != 0) + { + result |= GenericOperations.GENERIC_READ; + } + if ((access & FileAccess.Write) != 0) + { + result |= GenericOperations.GENERIC_WRITE; + } + if (fileMode == FileMode.Append) + { + result |= 0x0004; // FILE_APPEND_DATA + } + if ((options & FileOptions.Asynchronous) == 0) + { + result |= 0x00100000; // SYNCHRONIZE, requried by FILE_SYNCHRONOUS_IO_NONALERT + } + + return result; + } + + private static uint GetFileAttributes(FileOptions options) + { + uint result = 0; + + if ((options & FileOptions.Encrypted) != 0) + { + result |= 0x00004000; // FILE_ATTRIBUTE_ENCRYPTED + } + + return result; + } + + // FileShare.Inheritable is handled in GetObjectAttributes + private static uint GetShareAccess(FileShare share) + { + uint result = 0; + + if ((share & FileShare.Read) != 0) + { + result |= 1; // FILE_SHARE_READ + } + if ((share & FileShare.Write) != 0) + { + result |= 2; // FILE_SHARE_WRITE + } + if ((share & FileShare.Delete) != 0) + { + result |= 4; // FILE_SHARE_DELETE + } + + // https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile + // "If the original caller of NtCreateFile does not specify FILE_SHARE_READ, FILE_SHARE_WRITE, or FILE_SHARE_DELETE, + // no other open operations can be performed on the file; that is, the original caller is given exclusive access to the file." + // which is how we get FileShare.None working + + return result; + } + + private static uint GetCreateDisposition(FileMode mode) + { + switch (mode) + { + case FileMode.CreateNew: + return 2; // FILE_CREATE + case FileMode.Create: + return 0; // FILE_SUPERSEDE + case FileMode.OpenOrCreate: + case FileMode.Append: // has extra handling in GetDesiredAccess + return 3; // FILE_OPEN_IF + case FileMode.Truncate: + return 4; // FILE_OVERWRITE + default: + Debug.Assert(mode == FileMode.Open); // the enum value is validated in FileStream ctor + return 1; // FILE_OPEN + } + } + + // FileOptions.Encryptend is handled in GetFileAttributes + private static uint 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 + uint result = 0x00000040; // FILE_NON_DIRECTORY_FILE + + if ((options & FileOptions.WriteThrough) != 0) + { + result |= 0x00000002; // FILE_WRITE_THROUGH + } + if ((options & FileOptions.RandomAccess) != 0) + { + result |= 0x00000800; // FILE_RANDOM_ACCESS + } + if ((options & FileOptions.SequentialScan) != 0) + { + result |= 0x00000004; // FILE_SEQUENTIAL_ONLY + } + if ((options & FileOptions.DeleteOnClose) != 0) + { + result |= 0x00001000; // 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 |= 0x00000020; // FILE_SYNCHRONOUS_IO_NONALERT, has extra handling in GetDesiredAccess + } + if (((int)options & 0x20000000) != 0) // NoBuffering + { + result |= 0x00000008; // FILE_NO_INTERMEDIATE_BUFFERING + } + + return result; + } } } diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs new file mode 100644 index 00000000000000..129395f0de84c6 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs @@ -0,0 +1,18 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + [StructLayout(LayoutKind.Sequential)] + internal struct IO_STATUS_BLOCK + { + internal uint Status; + internal IntPtr Information; + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs new file mode 100644 index 00000000000000..535976bac5ab17 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff557749.aspx + [StructLayout(LayoutKind.Sequential)] + internal unsafe struct OBJECT_ATTRIBUTES + { + internal uint Length; + internal IntPtr RootDirectory; + internal UNICODE_STRING* ObjectName; + internal uint Attributes; + internal IntPtr SecurityDescriptor; + internal IntPtr SecurityQualityOfService; + + internal unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, uint attributes) + { + Length = (uint)sizeof(OBJECT_ATTRIBUTES); + RootDirectory = IntPtr.Zero; + ObjectName = objectName; + Attributes = attributes; + SecurityDescriptor = IntPtr.Zero; + SecurityQualityOfService = IntPtr.Zero; + } + } + } +} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs new file mode 100644 index 00000000000000..9c56fcb77adff1 --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs @@ -0,0 +1,27 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380518.aspx + // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal unsafe struct UNICODE_STRING + { + internal ushort Length; + internal ushort MaximumLength; + internal char* Buffer; + + internal unsafe UNICODE_STRING(char* buffer, int lengthInChars) + { + Length = checked((ushort)(lengthInChars * sizeof(char))); + MaximumLength = Length; + Buffer = buffer; + } + } + } +} 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..744336af3ec145 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 @@ -1513,6 +1513,15 @@ Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs + + Common\Interop\Windows\Kernel32\Interop.UNICODE_STRING.cs + + + Common\Interop\Windows\Kernel32\Interop.OBJECT_ATTRIBUTES.cs + + + Common\Interop\Windows\Kernel32\Interop.IO_STATUS_BLOCK.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 614775610412a5..d1eaf1f11f3cbb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs +++ b/src/libraries/System.Private.CoreLib/src/System/IO/FileStream.cs @@ -134,6 +134,36 @@ public FileStream(string path, FileMode mode, FileAccess access, FileShare share { } + /// + /// 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) 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 4e5543d7af9e3f..d207a577255c6e 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 @@ -56,35 +56,51 @@ internal static SafeFileHandle OpenHandle(string path, FileMode mode, FileAccess 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) + { + uint ntCreateFileResult = Interop.Kernel32.NtCreateFile(path, mode, access, share, options, allocationSize, out IntPtr fileHandle); + if (ntCreateFileResult == 0) + { + return ValidateFileHandle(new SafeFileHandle(fileHandle, ownsHandle: true), path, (options & FileOptions.Asynchronous) != 0); + } + else if (ntCreateFileResult == 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) + // increasing both the code complexity and the chance for breaking backward compatibility (by throwing a different exception than CreateFileW) + // 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); } } From b0332fb08cf748196bea22d4e47159dda19e9468 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Mon, 12 Apr 2021 15:07:34 +0200 Subject: [PATCH 5/8] fix the build --- .../Windows/Kernel32/Interop.CreateFile.cs | 173 ---------------- .../Windows/Kernel32/Interop.NtCreateFile.cs | 186 ++++++++++++++++++ .../System.Private.CoreLib.Shared.projitems | 3 + .../IO/Strategies/FileStreamHelpers.Unix.cs | 4 +- 4 files changed, 191 insertions(+), 175 deletions(-) create mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs index 6e88ab929e1108..ce5bfd9fd5321f 100644 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.CreateFile.cs @@ -3,7 +3,6 @@ using Microsoft.Win32.SafeHandles; using System; -using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -24,22 +23,6 @@ private static extern unsafe SafeFileHandle CreateFilePrivate( int dwFlagsAndAttributes, IntPtr hTemplateFile); - // https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntcreatefile - // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntcreatefile - [DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)] - private static unsafe extern uint NtCreateFile( - out IntPtr fileHandle, - int desiredAccess, - ref OBJECT_ATTRIBUTES objectAttributes, - out IO_STATUS_BLOCK ioStatusBlock, - long* allocationSize, - uint fileAttributes, - uint shareAccess, - uint createDisposition, - uint createOptions, - void* extendedAttributesBuffer, - uint extendedAttributesLength); - internal static unsafe SafeFileHandle CreateFile( string lpFileName, int dwDesiredAccess, @@ -63,161 +46,5 @@ internal static unsafe SafeFileHandle CreateFile( lpFileName = PathInternal.EnsureExtendedPrefixIfNeeded(lpFileName); return CreateFilePrivate(lpFileName, dwDesiredAccess, dwShareMode, null, dwCreationDisposition, dwFlagsAndAttributes, IntPtr.Zero); } - - internal static unsafe uint NtCreateFile(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize, out IntPtr fileHandle) - { - string prefixedAbsolutePath = PathInternal.IsExtended(path) - ? path - : @"\??\" + Path.GetFullPath(path); // TODO: we might consider getting rid of this managed allocation - - fixed (char* filePath = prefixedAbsolutePath) - { - UNICODE_STRING unicodeString = new UNICODE_STRING(filePath, prefixedAbsolutePath.Length); - OBJECT_ATTRIBUTES objectAttributes = new OBJECT_ATTRIBUTES(&unicodeString, GetObjectAttributes(share)); - - return NtCreateFile( - fileHandle: out fileHandle, - desiredAccess: GetDesiredAccess(access, mode, options), - objectAttributes: ref objectAttributes, - ioStatusBlock: out _, - allocationSize: &allocationSize, - fileAttributes: GetFileAttributes(options), - shareAccess: GetShareAccess(share), - createDisposition: GetCreateDisposition(mode), - createOptions: GetCreateOptions(options), - extendedAttributesBuffer: default, - extendedAttributesLength: default); - } - } - - private static uint GetObjectAttributes(FileShare share) - { - uint result = 0;// 0x00000040; // Lookups for this object should be case insensitive. [OBJ_CASE_INSENSITIVE] - - if ((share & FileShare.Inheritable) != 0 ) - { - result |= 0x00000002; - } - - return result; - } - - private static int GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options) - { - int result = 0; - - if ((access & FileAccess.Read) != 0) - { - result |= GenericOperations.GENERIC_READ; - } - if ((access & FileAccess.Write) != 0) - { - result |= GenericOperations.GENERIC_WRITE; - } - if (fileMode == FileMode.Append) - { - result |= 0x0004; // FILE_APPEND_DATA - } - if ((options & FileOptions.Asynchronous) == 0) - { - result |= 0x00100000; // SYNCHRONIZE, requried by FILE_SYNCHRONOUS_IO_NONALERT - } - - return result; - } - - private static uint GetFileAttributes(FileOptions options) - { - uint result = 0; - - if ((options & FileOptions.Encrypted) != 0) - { - result |= 0x00004000; // FILE_ATTRIBUTE_ENCRYPTED - } - - return result; - } - - // FileShare.Inheritable is handled in GetObjectAttributes - private static uint GetShareAccess(FileShare share) - { - uint result = 0; - - if ((share & FileShare.Read) != 0) - { - result |= 1; // FILE_SHARE_READ - } - if ((share & FileShare.Write) != 0) - { - result |= 2; // FILE_SHARE_WRITE - } - if ((share & FileShare.Delete) != 0) - { - result |= 4; // FILE_SHARE_DELETE - } - - // https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile - // "If the original caller of NtCreateFile does not specify FILE_SHARE_READ, FILE_SHARE_WRITE, or FILE_SHARE_DELETE, - // no other open operations can be performed on the file; that is, the original caller is given exclusive access to the file." - // which is how we get FileShare.None working - - return result; - } - - private static uint GetCreateDisposition(FileMode mode) - { - switch (mode) - { - case FileMode.CreateNew: - return 2; // FILE_CREATE - case FileMode.Create: - return 0; // FILE_SUPERSEDE - case FileMode.OpenOrCreate: - case FileMode.Append: // has extra handling in GetDesiredAccess - return 3; // FILE_OPEN_IF - case FileMode.Truncate: - return 4; // FILE_OVERWRITE - default: - Debug.Assert(mode == FileMode.Open); // the enum value is validated in FileStream ctor - return 1; // FILE_OPEN - } - } - - // FileOptions.Encryptend is handled in GetFileAttributes - private static uint 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 - uint result = 0x00000040; // FILE_NON_DIRECTORY_FILE - - if ((options & FileOptions.WriteThrough) != 0) - { - result |= 0x00000002; // FILE_WRITE_THROUGH - } - if ((options & FileOptions.RandomAccess) != 0) - { - result |= 0x00000800; // FILE_RANDOM_ACCESS - } - if ((options & FileOptions.SequentialScan) != 0) - { - result |= 0x00000004; // FILE_SEQUENTIAL_ONLY - } - if ((options & FileOptions.DeleteOnClose) != 0) - { - result |= 0x00001000; // 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 |= 0x00000020; // FILE_SYNCHRONOUS_IO_NONALERT, has extra handling in GetDesiredAccess - } - if (((int)options & 0x20000000) != 0) // NoBuffering - { - result |= 0x00000008; // FILE_NO_INTERMEDIATE_BUFFERING - } - - return result; - } } } diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs new file mode 100644 index 00000000000000..7d0417492651be --- /dev/null +++ b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs @@ -0,0 +1,186 @@ +// 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; +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; + +internal static partial class Interop +{ + internal static partial class Kernel32 + { + // https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntcreatefile + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntcreatefile + [DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)] + private static unsafe extern uint NtCreateFile( + out IntPtr fileHandle, + int desiredAccess, + ref OBJECT_ATTRIBUTES objectAttributes, + out IO_STATUS_BLOCK ioStatusBlock, + long* allocationSize, + uint fileAttributes, + uint shareAccess, + uint createDisposition, + uint createOptions, + void* extendedAttributesBuffer, + uint extendedAttributesLength); + + internal static unsafe uint NtCreateFile(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize, out IntPtr fileHandle) + { + string prefixedAbsolutePath = PathInternal.IsExtended(path) + ? path + : @"\??\" + Path.GetFullPath(path); // TODO: we might consider getting rid of this managed allocation + + fixed (char* filePath = prefixedAbsolutePath) + { + UNICODE_STRING unicodeString = new UNICODE_STRING(filePath, prefixedAbsolutePath.Length); + OBJECT_ATTRIBUTES objectAttributes = new OBJECT_ATTRIBUTES(&unicodeString, GetObjectAttributes(share)); + + return NtCreateFile( + fileHandle: out fileHandle, + desiredAccess: GetDesiredAccess(access, mode, options), + objectAttributes: ref objectAttributes, + ioStatusBlock: out _, + allocationSize: &allocationSize, + fileAttributes: GetFileAttributes(options), + shareAccess: GetShareAccess(share), + createDisposition: GetCreateDisposition(mode), + createOptions: GetCreateOptions(options), + extendedAttributesBuffer: default, + extendedAttributesLength: default); + } + } + + private static uint GetObjectAttributes(FileShare share) + { + uint result = 0;// 0x00000040; // Lookups for this object should be case insensitive. [OBJ_CASE_INSENSITIVE] + + if ((share & FileShare.Inheritable) != 0 ) + { + result |= 0x00000002; + } + + return result; + } + + private static int GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options) + { + int result = 0; + + if ((access & FileAccess.Read) != 0) + { + result |= GenericOperations.GENERIC_READ; + } + if ((access & FileAccess.Write) != 0) + { + result |= GenericOperations.GENERIC_WRITE; + } + if (fileMode == FileMode.Append) + { + result |= 0x0004; // FILE_APPEND_DATA + } + if ((options & FileOptions.Asynchronous) == 0) + { + result |= 0x00100000; // SYNCHRONIZE, requried by FILE_SYNCHRONOUS_IO_NONALERT + } + + return result; + } + + private static uint GetFileAttributes(FileOptions options) + { + uint result = 0; + + if ((options & FileOptions.Encrypted) != 0) + { + result |= 0x00004000; // FILE_ATTRIBUTE_ENCRYPTED + } + + return result; + } + + // FileShare.Inheritable is handled in GetObjectAttributes + private static uint GetShareAccess(FileShare share) + { + uint result = 0; + + if ((share & FileShare.Read) != 0) + { + result |= 1; // FILE_SHARE_READ + } + if ((share & FileShare.Write) != 0) + { + result |= 2; // FILE_SHARE_WRITE + } + if ((share & FileShare.Delete) != 0) + { + result |= 4; // FILE_SHARE_DELETE + } + + // https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile + // "If the original caller of NtCreateFile does not specify FILE_SHARE_READ, FILE_SHARE_WRITE, or FILE_SHARE_DELETE, + // no other open operations can be performed on the file; that is, the original caller is given exclusive access to the file." + // which is how we get FileShare.None working + + return result; + } + + private static uint GetCreateDisposition(FileMode mode) + { + switch (mode) + { + case FileMode.CreateNew: + return 2; // FILE_CREATE + case FileMode.Create: + return 0; // FILE_SUPERSEDE + case FileMode.OpenOrCreate: + case FileMode.Append: // has extra handling in GetDesiredAccess + return 3; // FILE_OPEN_IF + case FileMode.Truncate: + return 4; // FILE_OVERWRITE + default: + Debug.Assert(mode == FileMode.Open); // the enum value is validated in FileStream ctor + return 1; // FILE_OPEN + } + } + + // FileOptions.Encryptend is handled in GetFileAttributes + private static uint 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 + uint result = 0x00000040; // FILE_NON_DIRECTORY_FILE + + if ((options & FileOptions.WriteThrough) != 0) + { + result |= 0x00000002; // FILE_WRITE_THROUGH + } + if ((options & FileOptions.RandomAccess) != 0) + { + result |= 0x00000800; // FILE_RANDOM_ACCESS + } + if ((options & FileOptions.SequentialScan) != 0) + { + result |= 0x00000004; // FILE_SEQUENTIAL_ONLY + } + if ((options & FileOptions.DeleteOnClose) != 0) + { + result |= 0x00001000; // 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 |= 0x00000020; // FILE_SYNCHRONOUS_IO_NONALERT, has extra handling in GetDesiredAccess + } + if (((int)options & 0x20000000) != 0) // NoBuffering + { + result |= 0x00000008; // FILE_NO_INTERMEDIATE_BUFFERING + } + + return result; + } + } +} 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 744336af3ec145..cf7e505954723c 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\Kernel32\Interop.NtCreateFile.cs + Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs 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 64522ea320fee6..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,8 +17,8 @@ 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, long allocationSize) { From 5af38a92109d9430c5a6657d430c9d50366b79d3 Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 13 Apr 2021 12:31:40 +0200 Subject: [PATCH 6/8] remove duplicated Interop code --- .../Kernel32/Interop.IO_STATUS_BLOCK.cs | 18 -- .../Windows/Kernel32/Interop.NtCreateFile.cs | 186 ------------------ .../Kernel32/Interop.OBJECT_ATTRIBUTES.cs | 33 ---- .../Kernel32/Interop.UNICODE_STRING.cs | 27 --- .../Windows/NtDll/Interop.NtCreateFile.cs | 122 +++++++++++- .../Enumeration/FileSystemEnumerator.Win32.cs | 6 +- .../System.Private.CoreLib.Shared.projitems | 15 +- .../Strategies/FileStreamHelpers.Windows.cs | 8 +- 8 files changed, 126 insertions(+), 289 deletions(-) delete mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs delete mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs delete mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs delete mode 100644 src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs deleted file mode 100644 index 129395f0de84c6..00000000000000 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.IO_STATUS_BLOCK.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class Kernel32 - { - [StructLayout(LayoutKind.Sequential)] - internal struct IO_STATUS_BLOCK - { - internal uint Status; - internal IntPtr Information; - } - } -} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs deleted file mode 100644 index 7d0417492651be..00000000000000 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.NtCreateFile.cs +++ /dev/null @@ -1,186 +0,0 @@ -// 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; -using System; -using System.Diagnostics; -using System.IO; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class Kernel32 - { - // https://docs.microsoft.com/en-us/windows/desktop/api/winternl/nf-winternl-ntcreatefile - // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/ntifs/nf-ntifs-ntcreatefile - [DllImport(Libraries.NtDll, CharSet = CharSet.Unicode, ExactSpelling = true)] - private static unsafe extern uint NtCreateFile( - out IntPtr fileHandle, - int desiredAccess, - ref OBJECT_ATTRIBUTES objectAttributes, - out IO_STATUS_BLOCK ioStatusBlock, - long* allocationSize, - uint fileAttributes, - uint shareAccess, - uint createDisposition, - uint createOptions, - void* extendedAttributesBuffer, - uint extendedAttributesLength); - - internal static unsafe uint NtCreateFile(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize, out IntPtr fileHandle) - { - string prefixedAbsolutePath = PathInternal.IsExtended(path) - ? path - : @"\??\" + Path.GetFullPath(path); // TODO: we might consider getting rid of this managed allocation - - fixed (char* filePath = prefixedAbsolutePath) - { - UNICODE_STRING unicodeString = new UNICODE_STRING(filePath, prefixedAbsolutePath.Length); - OBJECT_ATTRIBUTES objectAttributes = new OBJECT_ATTRIBUTES(&unicodeString, GetObjectAttributes(share)); - - return NtCreateFile( - fileHandle: out fileHandle, - desiredAccess: GetDesiredAccess(access, mode, options), - objectAttributes: ref objectAttributes, - ioStatusBlock: out _, - allocationSize: &allocationSize, - fileAttributes: GetFileAttributes(options), - shareAccess: GetShareAccess(share), - createDisposition: GetCreateDisposition(mode), - createOptions: GetCreateOptions(options), - extendedAttributesBuffer: default, - extendedAttributesLength: default); - } - } - - private static uint GetObjectAttributes(FileShare share) - { - uint result = 0;// 0x00000040; // Lookups for this object should be case insensitive. [OBJ_CASE_INSENSITIVE] - - if ((share & FileShare.Inheritable) != 0 ) - { - result |= 0x00000002; - } - - return result; - } - - private static int GetDesiredAccess(FileAccess access, FileMode fileMode, FileOptions options) - { - int result = 0; - - if ((access & FileAccess.Read) != 0) - { - result |= GenericOperations.GENERIC_READ; - } - if ((access & FileAccess.Write) != 0) - { - result |= GenericOperations.GENERIC_WRITE; - } - if (fileMode == FileMode.Append) - { - result |= 0x0004; // FILE_APPEND_DATA - } - if ((options & FileOptions.Asynchronous) == 0) - { - result |= 0x00100000; // SYNCHRONIZE, requried by FILE_SYNCHRONOUS_IO_NONALERT - } - - return result; - } - - private static uint GetFileAttributes(FileOptions options) - { - uint result = 0; - - if ((options & FileOptions.Encrypted) != 0) - { - result |= 0x00004000; // FILE_ATTRIBUTE_ENCRYPTED - } - - return result; - } - - // FileShare.Inheritable is handled in GetObjectAttributes - private static uint GetShareAccess(FileShare share) - { - uint result = 0; - - if ((share & FileShare.Read) != 0) - { - result |= 1; // FILE_SHARE_READ - } - if ((share & FileShare.Write) != 0) - { - result |= 2; // FILE_SHARE_WRITE - } - if ((share & FileShare.Delete) != 0) - { - result |= 4; // FILE_SHARE_DELETE - } - - // https://docs.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntcreatefile - // "If the original caller of NtCreateFile does not specify FILE_SHARE_READ, FILE_SHARE_WRITE, or FILE_SHARE_DELETE, - // no other open operations can be performed on the file; that is, the original caller is given exclusive access to the file." - // which is how we get FileShare.None working - - return result; - } - - private static uint GetCreateDisposition(FileMode mode) - { - switch (mode) - { - case FileMode.CreateNew: - return 2; // FILE_CREATE - case FileMode.Create: - return 0; // FILE_SUPERSEDE - case FileMode.OpenOrCreate: - case FileMode.Append: // has extra handling in GetDesiredAccess - return 3; // FILE_OPEN_IF - case FileMode.Truncate: - return 4; // FILE_OVERWRITE - default: - Debug.Assert(mode == FileMode.Open); // the enum value is validated in FileStream ctor - return 1; // FILE_OPEN - } - } - - // FileOptions.Encryptend is handled in GetFileAttributes - private static uint 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 - uint result = 0x00000040; // FILE_NON_DIRECTORY_FILE - - if ((options & FileOptions.WriteThrough) != 0) - { - result |= 0x00000002; // FILE_WRITE_THROUGH - } - if ((options & FileOptions.RandomAccess) != 0) - { - result |= 0x00000800; // FILE_RANDOM_ACCESS - } - if ((options & FileOptions.SequentialScan) != 0) - { - result |= 0x00000004; // FILE_SEQUENTIAL_ONLY - } - if ((options & FileOptions.DeleteOnClose) != 0) - { - result |= 0x00001000; // 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 |= 0x00000020; // FILE_SYNCHRONOUS_IO_NONALERT, has extra handling in GetDesiredAccess - } - if (((int)options & 0x20000000) != 0) // NoBuffering - { - result |= 0x00000008; // FILE_NO_INTERMEDIATE_BUFFERING - } - - return result; - } - } -} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs deleted file mode 100644 index 535976bac5ab17..00000000000000 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.OBJECT_ATTRIBUTES.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class Kernel32 - { - // https://msdn.microsoft.com/en-us/library/windows/hardware/ff557749.aspx - [StructLayout(LayoutKind.Sequential)] - internal unsafe struct OBJECT_ATTRIBUTES - { - internal uint Length; - internal IntPtr RootDirectory; - internal UNICODE_STRING* ObjectName; - internal uint Attributes; - internal IntPtr SecurityDescriptor; - internal IntPtr SecurityQualityOfService; - - internal unsafe OBJECT_ATTRIBUTES(UNICODE_STRING* objectName, uint attributes) - { - Length = (uint)sizeof(OBJECT_ATTRIBUTES); - RootDirectory = IntPtr.Zero; - ObjectName = objectName; - Attributes = attributes; - SecurityDescriptor = IntPtr.Zero; - SecurityQualityOfService = IntPtr.Zero; - } - } - } -} diff --git a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs b/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs deleted file mode 100644 index 9c56fcb77adff1..00000000000000 --- a/src/libraries/Common/src/Interop/Windows/Kernel32/Interop.UNICODE_STRING.cs +++ /dev/null @@ -1,27 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Runtime.InteropServices; - -internal static partial class Interop -{ - internal static partial class Kernel32 - { - // https://msdn.microsoft.com/en-us/library/windows/desktop/aa380518.aspx - // https://msdn.microsoft.com/en-us/library/windows/hardware/ff564879.aspx - [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] - internal unsafe struct UNICODE_STRING - { - internal ushort Length; - internal ushort MaximumLength; - internal char* Buffer; - - internal unsafe UNICODE_STRING(char* buffer, int lengthInChars) - { - Length = checked((ushort)(lengthInChars * sizeof(char))); - MaximumLength = Length; - Buffer = buffer; - } - } - } -} 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..48415c06e82403 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(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) + => CreateFile( + path: PathInternal.IsExtended(path) ? path : @"\??\" + Path.GetFullPath(path), // TODO: we might consider getting rid of this managed allocation, + 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.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.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index cf7e505954723c..4ce2158ab05847 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,8 +1348,8 @@ Common\Interop\Windows\Kernel32\Interop.CreateFile.cs - - Common\Interop\Windows\Kernel32\Interop.NtCreateFile.cs + + Common\Interop\Windows\Kernel32\NtDll\Interop.NtCreateFile.cs Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs @@ -1516,14 +1516,11 @@ Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs - - Common\Interop\Windows\Kernel32\Interop.UNICODE_STRING.cs + + Common\Interop\Windows\Interop.UNICODE_STRING.cs - - Common\Interop\Windows\Kernel32\Interop.OBJECT_ATTRIBUTES.cs - - - Common\Interop\Windows\Kernel32\Interop.IO_STATUS_BLOCK.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/Strategies/FileStreamHelpers.Windows.cs b/src/libraries/System.Private.CoreLib/src/System/IO/Strategies/FileStreamHelpers.Windows.cs index d207a577255c6e..525a52c85332c3 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 @@ -62,19 +62,19 @@ private static unsafe SafeFileHandle CreateFileOpenHandle(string path, FileMode if (allocationSize > 0) { - uint ntCreateFileResult = Interop.Kernel32.NtCreateFile(path, mode, access, share, options, allocationSize, out IntPtr fileHandle); - if (ntCreateFileResult == 0) + (uint ntStatus, IntPtr fileHandle) = Interop.NtDll.CreateFile(path, mode, access, share, options, allocationSize); + if (ntStatus == 0) { return ValidateFileHandle(new SafeFileHandle(fileHandle, ownsHandle: true), path, (options & FileOptions.Asynchronous) != 0); } - else if (ntCreateFileResult == ERROR_STATUS_DISK_FULL) + else if (ntStatus == ERROR_STATUS_DISK_FULL) { throw new IOException(SR.Format(SR.IO_DiskFull_Path_AllocationSize, path, allocationSize)); } // 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) - // increasing both the code complexity and the chance for breaking backward compatibility (by throwing a different exception than CreateFileW) + // or using RtlNtStatusToDosError & GetExceptionForWin32Error // the code falls back to CreateFileW that just throws the right exception. } From ecad62719a4027e849d988505e28f5e69fb5e80e Mon Sep 17 00:00:00 2001 From: Adam Sitnik Date: Tue, 13 Apr 2021 16:11:01 +0200 Subject: [PATCH 7/8] fix the full framework build --- .../Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs | 4 ++-- .../src/System.Private.CoreLib.Shared.projitems | 2 +- .../src/System/IO/Strategies/FileStreamHelpers.Windows.cs | 3 ++- 3 files changed, 5 insertions(+), 4 deletions(-) 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 48415c06e82403..bdc85f848e9167 100644 --- a/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs +++ b/src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs @@ -70,9 +70,9 @@ internal static unsafe (uint status, IntPtr handle) CreateFile( } } - internal static unsafe (uint status, IntPtr handle) CreateFile(string path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) + internal static unsafe (uint status, IntPtr handle) CreateFile(ReadOnlySpan path, FileMode mode, FileAccess access, FileShare share, FileOptions options, long allocationSize) => CreateFile( - path: PathInternal.IsExtended(path) ? path : @"\??\" + Path.GetFullPath(path), // TODO: we might consider getting rid of this managed allocation, + path: path, rootDirectory: IntPtr.Zero, createDisposition: GetCreateDisposition(mode), desiredAccess: GetDesiredAccess(access, mode, options), 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 4ce2158ab05847..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 @@ -1349,7 +1349,7 @@ Common\Interop\Windows\Kernel32\Interop.CreateFile.cs - Common\Interop\Windows\Kernel32\NtDll\Interop.NtCreateFile.cs + Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs 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 525a52c85332c3..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 @@ -62,7 +62,8 @@ private static unsafe SafeFileHandle CreateFileOpenHandle(string path, FileMode if (allocationSize > 0) { - (uint ntStatus, IntPtr fileHandle) = Interop.NtDll.CreateFile(path, mode, access, share, options, allocationSize); + 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); From 12d7f654d5b4fa4cf115f14484f009e945117375 Mon Sep 17 00:00:00 2001 From: David Cantu Date: Mon, 19 Apr 2021 23:05:09 -0700 Subject: [PATCH 8/8] Use file preallocation on ZipFileExtensions.ExtractToFile --- .../IO/Compression/ZipFileExtensions.ZipArchiveEntry.Extract.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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);