Skip to content
This repository was archived by the owner on Jul 26, 2023. It is now read-only.

Add DeviceIoControlAsync helper method #504

Merged
merged 9 commits into from
Jul 12, 2020
Merged
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
94 changes: 94 additions & 0 deletions src/Kernel32.Tests/storebanned/Kernel32Facts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.Linq;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using PInvoke;
using Xunit;
using static PInvoke.Kernel32;
Expand Down Expand Up @@ -1167,6 +1168,99 @@ public unsafe void DeviceIOControl_Works()
}
}

[Fact]
public async Task DeviceIOControlAsync_NotOverlapped_Works()
{
const uint IOCTL_DISK_GET_DRIVE_GEOMETRY = 0x070000;
const string drive = @"\\.\PhysicalDrive0";

var data = new DISK_GEOMETRY[1];

using (var device = CreateFile(
filename: drive,
access: 0,
share: Kernel32.FileShare.FILE_SHARE_READ | Kernel32.FileShare.FILE_SHARE_WRITE,
securityAttributes: IntPtr.Zero,
creationDisposition: CreationDisposition.OPEN_EXISTING,
flagsAndAttributes: 0,
SafeObjectHandle.Null))
{
Assert.False(device.IsInvalid);

var ret = (int)await DeviceIoControlAsync<byte, DISK_GEOMETRY>(
device,
(int)IOCTL_DISK_GET_DRIVE_GEOMETRY,
Array.Empty<byte>(),
data,
CancellationToken.None);

Assert.Equal(Marshal.SizeOf<DISK_GEOMETRY>(), ret);

var pdg = data[0];
Assert.NotEqual(0u, pdg.BytesPerSector);
Assert.NotEqual(0, pdg.Cylinders);
Assert.Equal(MEDIA_TYPE.FixedMedia, pdg.MediaType);
Assert.NotEqual(0u, pdg.SectorsPerTrack);
Assert.NotEqual(0u, pdg.TracksPerCylinder);
}
}

[Fact]
public async Task DeviceIOControlAsync_Overlapped_Works()
{
const uint FSCTL_SET_ZERO_DATA = 0x000980c8;
string fileName = Path.GetTempFileName();

try
{
using (var file = CreateFile(
filename: fileName,
access: Kernel32.ACCESS_MASK.GenericRight.GENERIC_READ | ACCESS_MASK.GenericRight.GENERIC_WRITE,
share: Kernel32.FileShare.FILE_SHARE_READ | Kernel32.FileShare.FILE_SHARE_WRITE,
securityAttributes: IntPtr.Zero,
creationDisposition: CreationDisposition.CREATE_ALWAYS,
flagsAndAttributes: CreateFileFlags.FILE_FLAG_OVERLAPPED,
SafeObjectHandle.Null))
{
Assert.False(file.IsInvalid);

Assert.True(ThreadPool.BindHandle(file));

var data = new FILE_ZERO_DATA_INFORMATION[]
{
new FILE_ZERO_DATA_INFORMATION { BeyondFinalZero = int.MaxValue },
};

uint ret = await Kernel32.DeviceIoControlAsync<FILE_ZERO_DATA_INFORMATION, byte>(
file,
(int)FSCTL_SET_ZERO_DATA,
data,
null,
CancellationToken.None);

Assert.Equal(0u, ret);
}
}
finally
{
File.Delete(fileName);
}
}

[Fact]
public async Task DeviceIOControlAsync_Exception_Works()
{
const uint IOCTL_DISK_GET_DRIVE_GEOMETRY = 0x070000;

await Assert.ThrowsAsync<Win32Exception>(() =>
DeviceIoControlAsync<byte, byte>(
SafeObjectHandle.Invalid,
(int)IOCTL_DISK_GET_DRIVE_GEOMETRY,
Array.Empty<byte>(),
Array.Empty<byte>(),
CancellationToken.None).AsTask()).ConfigureAwait(false);
}

/// <summary>
/// Helper for <see cref="CreateThread_Test"/>, <see cref="CreateRemoteThread_PseudoTest"/> and
/// <see cref="CreateRemoteThreadEx_PseudoTest"/> tests.
Expand Down
6 changes: 5 additions & 1 deletion src/Kernel32/Kernel32.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
<Project Sdk="MSBuild.Sdk.Extras">
<PropertyGroup>
<TargetFrameworks>$(PlatformAndPortableFrameworks)</TargetFrameworks>
<TargetFrameworks>$(PlatformAndPortableFrameworks);net46</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Windows.Core\Windows.Core.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.4" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.5.4" />
</ItemGroup>
</Project>
7 changes: 6 additions & 1 deletion src/Kernel32/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ PInvoke.Kernel32.DISK_GEOMETRY.DISK_GEOMETRY() -> void
PInvoke.Kernel32.DISK_GEOMETRY.MediaType -> PInvoke.Kernel32.MEDIA_TYPE
PInvoke.Kernel32.DISK_GEOMETRY.SectorsPerTrack -> uint
PInvoke.Kernel32.DISK_GEOMETRY.TracksPerCylinder -> uint
PInvoke.Kernel32.FILE_ZERO_DATA_INFORMATION
PInvoke.Kernel32.FILE_ZERO_DATA_INFORMATION.BeyondFinalZero -> long
PInvoke.Kernel32.FILE_ZERO_DATA_INFORMATION.FILE_ZERO_DATA_INFORMATION() -> void
PInvoke.Kernel32.FILE_ZERO_DATA_INFORMATION.FileOffset -> long
PInvoke.Kernel32.FileSystemFlags
PInvoke.Kernel32.FileSystemFlags.FILE_CASE_PRESERVED_NAMES = 2 -> PInvoke.Kernel32.FileSystemFlags
PInvoke.Kernel32.FileSystemFlags.FILE_CASE_SENSITIVE_SEARCH = 1 -> PInvoke.Kernel32.FileSystemFlags
Expand Down Expand Up @@ -208,6 +212,7 @@ static PInvoke.Kernel32.CreateRemoteThreadEx(System.IntPtr hProcess, System.IntP
static PInvoke.Kernel32.CreateThread(PInvoke.Kernel32.SECURITY_ATTRIBUTES? lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
static PInvoke.Kernel32.CreateThread(PInvoke.Kernel32.SECURITY_ATTRIBUTES? lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, void* lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, ref uint? lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
static PInvoke.Kernel32.CreateThread(System.IntPtr lpThreadAttributes, System.UIntPtr dwStackSize, PInvoke.Kernel32.THREAD_START_ROUTINE lpStartAddress, System.IntPtr lpParameter, PInvoke.Kernel32.CreateThreadFlags dwCreationFlags, System.IntPtr lpThreadId) -> PInvoke.Kernel32.SafeObjectHandle
static PInvoke.Kernel32.DeviceIoControlAsync<TInput, TOutput>(PInvoke.Kernel32.SafeObjectHandle hDevice, uint dwIoControlCode, System.Memory<TInput> inBuffer, System.Memory<TOutput> outBuffer, System.Threading.CancellationToken cancellationToken) -> System.Threading.Tasks.ValueTask<uint>
static PInvoke.Kernel32.DosDateTimeToFileTime(ushort wFatDate, ushort wFatTime, System.IntPtr lpFileTime) -> bool
static PInvoke.Kernel32.DosDateTimeToFileTime(ushort wFatDate, ushort wFatTime, out PInvoke.Kernel32.FILETIME lpFileTime) -> bool
static PInvoke.Kernel32.FlushInstructionCache(PInvoke.Kernel32.SafeObjectHandle hProcess, System.IntPtr lpcBaseAddress, System.UIntPtr dwSize) -> bool
Expand Down Expand Up @@ -250,4 +255,4 @@ static extern PInvoke.Kernel32.SetFilePointerEx(PInvoke.Kernel32.SafeObjectHandl
static extern PInvoke.Kernel32.SetProcessInformation(PInvoke.Kernel32.SafeObjectHandle hProcess, PInvoke.Kernel32.PROCESS_INFORMATION_CLASS ProcessInformationClass, void* ProcessInformation, uint ProcessInformationSize) -> bool
static readonly PInvoke.Kernel32.PROCESS_MEMORY_EXHAUSTION_INFO.PME_FAILFAST_ON_COMMIT_FAIL_DISABLE -> System.UIntPtr
static readonly PInvoke.Kernel32.PROCESS_MEMORY_EXHAUSTION_INFO.PME_FAILFAST_ON_COMMIT_FAIL_ENABLE -> System.UIntPtr
static readonly PInvoke.Kernel32.SafePseudoConsoleHandle.Invalid -> PInvoke.Kernel32.SafePseudoConsoleHandle
static readonly PInvoke.Kernel32.SafePseudoConsoleHandle.Invalid -> PInvoke.Kernel32.SafePseudoConsoleHandle
135 changes: 135 additions & 0 deletions src/Kernel32/storebanned/Kernel32+DeviceIOControlOverlapped.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// Copyright © .NET Foundation and Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace PInvoke
{
using System;
using System.Buffers;
using System.Threading;
using System.Threading.Tasks;

/// <content>
/// Contains the <see cref="CreateFileFlags"/> nested type.
/// </content>
public partial class Kernel32
{
private class DeviceIOControlOverlapped<TInput, TOutput> : Overlapped
where TInput : struct
where TOutput : struct
{
private readonly Memory<TInput> input;
private readonly Memory<TOutput> output;

/// <summary>
/// The source for completing the <see cref="Completion"/> property.
/// </summary>
private readonly TaskCompletionSource<uint> completion = new TaskCompletionSource<uint>();

private unsafe NativeOverlapped* native;

/// <summary>
/// Initializes a new instance of the <see cref="DeviceIOControlOverlapped{TInput, TOutput}"/> class.
/// </summary>
/// <param name="input">
/// The input buffer.
/// </param>
/// <param name="output">
/// The output buffer.
/// </param>
internal DeviceIOControlOverlapped(Memory<TInput> input, Memory<TOutput> output)
Copy link
Contributor Author

@qmfrederik qmfrederik Jul 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, since this constructor (and the properties below) are defined on a class which is private to Kernel32, is there any difference between marking it as public or internal?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are subtle differences in Reflection, but the main reason is because public triggers "public API review" mode in my mind as public APIs are very hard to change later, and these are in fact not public APIs. The C# public keyword in private or internal types are in fact not public at all (unless they also implement an interface). So it's a misleading keyword to use, and makes code reviews more expensive. So I prefer internal for members of non-public types instead of public.

{
this.input = input;
this.output = output;
}

/// <summary>
/// Gets a <see cref="MemoryHandle"/> to the input buffer.
/// </summary>
internal MemoryHandle InputHandle { get; private set; }

/// <summary>
/// Gets a <see cref="MemoryHandle"/> to the output buffer.
/// </summary>
internal MemoryHandle OutputHandle { get; private set; }

/// <summary>
/// Gets the amount of bytes written.
/// </summary>
internal uint BytesWritten { get; private set; }

/// <summary>
/// Gets the error code returned by the device driver.
/// </summary>
internal uint ErrorCode { get; private set; }

/// <summary>
/// Gets a task whose result is the number of bytes transferred, or faults with the <see cref="Win32Exception"/> describing the failure.
/// </summary>
internal Task<uint> Completion => this.completion.Task;

/// <summary>
/// Packs the current instance of the <see cref="DeviceIOControlOverlapped{TInput, TOutput}"/> into a <see cref="NativeOverlapped"/>
/// structure and pins the input and output buffers.
/// </summary>
/// <returns>
/// A <see cref="NativeOverlapped"/> structure which can be used to call <see cref="Kernel32.DeviceIoControl(Kernel32.SafeObjectHandle, int, void*, int, void*, int, out int, Kernel32.OVERLAPPED*)"/>.
/// </returns>
internal unsafe NativeOverlapped* Pack()
{
this.InputHandle = this.input.Pin();
this.OutputHandle = this.output.Pin();

this.native = this.Pack(
this.DeviceIOControlCompletionCallback,
null);

return this.native;
}

/// <summary>
/// Unpacks the <see cref="NativeOverlapped"/> structure associated with this <see cref="DeviceIOControlOverlapped{TInput, TOutput}"/>
/// instance, frees the native memory used and unpins the input and output buffers.
/// </summary>
internal unsafe void Unpack()
{
Overlapped.Unpack(this.native);
Overlapped.Free(this.native);
this.native = null;

this.InputHandle.Dispose();
this.OutputHandle.Dispose();
}

/// <summary>
/// A callback which is invoked once the asynchronous I/O operation completes.
/// </summary>
/// <param name="errorCode">
/// The error code returned by the device driver.
/// </param>
/// <param name="numberOfBytesTransferred">
/// The number of bytes transferred.
/// </param>
/// <param name="nativeOverlapped">
/// A <see cref="NativeOverlapped"/> object representing an unmanaged pointer to the
/// native overlapped value type.
/// </param>
private unsafe void DeviceIOControlCompletionCallback(uint errorCode, uint numberOfBytesTransferred, NativeOverlapped* nativeOverlapped)
{
this.Unpack();

this.BytesWritten = numberOfBytesTransferred;
this.ErrorCode = errorCode;

if (this.ErrorCode != 0)
{
this.completion.SetException(
new Win32Exception((int)this.ErrorCode));
}
else
{
this.completion.SetResult(numberOfBytesTransferred);
}
}
}
}
}
29 changes: 29 additions & 0 deletions src/Kernel32/storebanned/Kernel32+FILE_ZERO_DATA_INFORMATION.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Copyright © .NET Foundation and Contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace PInvoke
{
using System;

/// <content>
/// Contains the <see cref="FILE_ZERO_DATA_INFORMATION "/> nested type.
/// </content>
public partial class Kernel32
{
/// <summary>
/// Contains a range of a file to set to zeros. This structure is used by the <c>FSCTL_SET_ZERO_DATA</c> control code.
/// </summary>
public struct FILE_ZERO_DATA_INFORMATION
{
/// <summary>
/// The file offset of the start of the range to set to zeros, in bytes.
/// </summary>
public long FileOffset;

/// <summary>
/// The byte offset of the first byte beyond the last zeroed byte.
/// </summary>
public long BeyondFinalZero;
}
}
}
Loading