Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
122 changes: 113 additions & 9 deletions src/libraries/Common/src/Interop/Windows/NtDll/Interop.NtCreateFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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<char> 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))
{
Expand All @@ -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,
Expand All @@ -67,6 +70,107 @@ internal static unsafe (int status, IntPtr handle) CreateFile(
}
}

internal static unsafe (uint status, IntPtr handle) CreateFile(ReadOnlySpan<char> 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;

/// <summary>
/// File creation disposition when calling directly to NT APIs.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,15 +60,15 @@ private unsafe bool GetData()

private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan<char> relativePath, string fullPath)
{
(int status, IntPtr handle) = Interop.NtDll.CreateFile(
(uint status, IntPtr handle) = Interop.NtDll.CreateFile(
relativePath,
_directoryHandle,
Interop.NtDll.CreateDisposition.FILE_OPEN,
Interop.NtDll.DesiredAccess.FILE_LIST_DIRECTORY | Interop.NtDll.DesiredAccess.SYNCHRONIZE,
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;
Expand All @@ -77,7 +77,7 @@ private unsafe IntPtr CreateRelativeDirectoryHandle(ReadOnlySpan<char> 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))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Stream> CreateStream(byte[] initialData, FileAccess access)
{
Expand All @@ -23,7 +24,7 @@ private Task<Stream> CreateStream(byte[] initialData, FileAccess access)
File.WriteAllBytes(path, initialData);
}

return Task.FromResult<Stream>(new FileStream(path, FileMode.OpenOrCreate, access, FileShare.None, BufferSize, Options));
return Task.FromResult<Stream>(new FileStream(path, FileMode.OpenOrCreate, access, FileShare.None, BufferSize, Options, AllocationSize));
}

protected override Task<Stream> CreateReadOnlyStreamCore(byte[] initialData) => CreateStream(initialData, FileAccess.Read);
Expand Down Expand Up @@ -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<IOException>(() => 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)]
Expand All @@ -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)]
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2644,6 +2644,9 @@
<data name="IO_AlreadyExists_Name" xml:space="preserve">
<value>Cannot create '{0}' because a file or directory with the same name already exists.</value>
</data>
<data name="IO_DiskFull_Path_AllocationSize" xml:space="preserve">
<value>Failed to create '{0}' with allocation size '{1}' because the disk was full.</value>
</data>
<data name="IO_BindHandleFailed" xml:space="preserve">
<value>BindHandle for ThreadPool failed on this handle.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1348,6 +1348,9 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CreateFile.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CreateFile.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\NtDll\Interop.NtCreateFile.cs">
<Link>Common\Interop\Windows\NtDll\Interop.NtCreateFile.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.CriticalSection.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.CriticalSection.cs</Link>
</Compile>
Expand Down Expand Up @@ -1513,6 +1516,12 @@
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SECURITY_ATTRIBUTES.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Interop.UNICODE_STRING.cs">
<Link>Common\Interop\Windows\Interop.UNICODE_STRING.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs">
<Link>Common\Interop\Windows\Interop.OBJECT_ATTRIBUTES.cs</Link>
</Compile>
<Compile Include="$(CommonPath)Interop\Windows\Kernel32\Interop.SecurityOptions.cs">
<Link>Common\Interop\Windows\Kernel32\Interop.SecurityOptions.cs</Link>
</Compile>
Expand Down
Loading