Add helper functions for pipes (#17566)
Split off from #17510: * `HandleWantsOverlappedIo` can be used to check if a handle requires overlapped IO. This is important, as `ReadFile` and `WriteFile` are documented to not work correctly if an overlapped handle is used without overlapped IO and vice versa. In my tests with pipes, this appears to be true. * `CreatePipe` creates a synchronous, unidirectional pipe. * `CreateOverlappedPipe` does what it says on the tin, while allowing you to specify the direction of the pipe (in, out, duplex). * `GetOverlappedResultSameThread` is largely the same as `GetOverlappedResult`, but adds back a neat optimization from the time before Windows 7. I thought it was neat.
This commit is contained in:
parent
5756df4d9b
commit
1d2ffe9109
|
@ -1205,6 +1205,7 @@ nouicompat
|
|||
nounihan
|
||||
NOYIELD
|
||||
NOZORDER
|
||||
NPFS
|
||||
nrcs
|
||||
NSTATUS
|
||||
ntapi
|
||||
|
|
|
@ -15,6 +15,12 @@ Author(s):
|
|||
|
||||
namespace Microsoft::Console::Utils
|
||||
{
|
||||
struct Pipe
|
||||
{
|
||||
wil::unique_hfile server;
|
||||
wil::unique_hfile client;
|
||||
};
|
||||
|
||||
// Function Description:
|
||||
// - Returns -1, 0 or +1 to indicate the sign of the passed-in value.
|
||||
template<typename T>
|
||||
|
@ -24,6 +30,10 @@ namespace Microsoft::Console::Utils
|
|||
}
|
||||
|
||||
bool IsValidHandle(const HANDLE handle) noexcept;
|
||||
bool HandleWantsOverlappedIo(HANDLE handle) noexcept;
|
||||
Pipe CreatePipe(DWORD bufferSize);
|
||||
Pipe CreateOverlappedPipe(DWORD openMode, DWORD bufferSize);
|
||||
HRESULT GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred) noexcept;
|
||||
|
||||
// Function Description:
|
||||
// - Clamps a long in between `min` and `SHRT_MAX`
|
||||
|
|
|
@ -4,13 +4,11 @@
|
|||
#include "precomp.h"
|
||||
#include "inc/utils.hpp"
|
||||
|
||||
#include <propsys.h>
|
||||
#include <til/string.h>
|
||||
#include <wil/token_helpers.h>
|
||||
|
||||
#include "inc/colorTable.hpp"
|
||||
|
||||
#include <wil/token_helpers.h>
|
||||
#include <til/string.h>
|
||||
|
||||
using namespace Microsoft::Console;
|
||||
|
||||
// Routine Description:
|
||||
|
@ -631,6 +629,208 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept
|
|||
return handle != nullptr && handle != INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
#define FileModeInformation (FILE_INFORMATION_CLASS)16
|
||||
|
||||
#define FILE_PIPE_BYTE_STREAM_TYPE 0x00000000
|
||||
#define FILE_PIPE_BYTE_STREAM_MODE 0x00000000
|
||||
#define FILE_PIPE_QUEUE_OPERATION 0x00000000
|
||||
|
||||
typedef struct _FILE_MODE_INFORMATION
|
||||
{
|
||||
ULONG Mode;
|
||||
} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION;
|
||||
|
||||
extern "C" NTSTATUS NTAPI NtQueryInformationFile(
|
||||
HANDLE FileHandle,
|
||||
PIO_STATUS_BLOCK IoStatusBlock,
|
||||
PVOID FileInformation,
|
||||
ULONG Length,
|
||||
FILE_INFORMATION_CLASS FileInformationClass);
|
||||
|
||||
extern "C" NTSTATUS NTAPI NtCreateNamedPipeFile(
|
||||
PHANDLE FileHandle,
|
||||
ULONG DesiredAccess,
|
||||
POBJECT_ATTRIBUTES ObjectAttributes,
|
||||
PIO_STATUS_BLOCK IoStatusBlock,
|
||||
ULONG ShareAccess,
|
||||
ULONG CreateDisposition,
|
||||
ULONG CreateOptions,
|
||||
ULONG NamedPipeType,
|
||||
ULONG ReadMode,
|
||||
ULONG CompletionMode,
|
||||
ULONG MaximumInstances,
|
||||
ULONG InboundQuota,
|
||||
ULONG OutboundQuota,
|
||||
PLARGE_INTEGER DefaultTimeout);
|
||||
|
||||
bool Utils::HandleWantsOverlappedIo(HANDLE handle) noexcept
|
||||
{
|
||||
IO_STATUS_BLOCK statusBlock;
|
||||
FILE_MODE_INFORMATION modeInfo;
|
||||
const auto status = NtQueryInformationFile(handle, &statusBlock, &modeInfo, sizeof(modeInfo), FileModeInformation);
|
||||
return status == 0 && WI_AreAllFlagsClear(modeInfo.Mode, FILE_SYNCHRONOUS_IO_ALERT | FILE_SYNCHRONOUS_IO_NONALERT);
|
||||
}
|
||||
|
||||
// Creates an anonymous pipe. Behaves like PIPE_ACCESS_INBOUND,
|
||||
// meaning the .server is for reading and the .client is for writing.
|
||||
Utils::Pipe Utils::CreatePipe(DWORD bufferSize)
|
||||
{
|
||||
wil::unique_hfile rx, tx;
|
||||
THROW_IF_WIN32_BOOL_FALSE(::CreatePipe(rx.addressof(), tx.addressof(), nullptr, bufferSize));
|
||||
return { std::move(rx), std::move(tx) };
|
||||
}
|
||||
|
||||
// Creates an overlapped anonymous pipe. openMode should be either:
|
||||
// * PIPE_ACCESS_INBOUND
|
||||
// * PIPE_ACCESS_OUTBOUND
|
||||
// * PIPE_ACCESS_DUPLEX
|
||||
//
|
||||
// I know, I know. MSDN infamously says
|
||||
// > Asynchronous (overlapped) read and write operations are not supported by anonymous pipes.
|
||||
// but that's a lie. The only reason they're not supported is because the Win32
|
||||
// API doesn't have a parameter where you could pass FILE_FLAG_OVERLAPPED!
|
||||
// So, we'll simply use the underlying NT APIs instead.
|
||||
//
|
||||
// Most code on the internet suggests creating named pipes with a random name,
|
||||
// but usually conveniently forgets to mention that named pipes require strict ACLs.
|
||||
// https://stackoverflow.com/q/60645 for instance contains a lot of poor advice.
|
||||
// Anonymous pipes also cannot be discovered via NtQueryDirectoryFile inside the NPFS driver,
|
||||
// whereas running a tool like Sysinternals' PipeList will return all those semi-named pipes.
|
||||
//
|
||||
// The code below contains comments to create unidirectional pipes.
|
||||
Utils::Pipe Utils::CreateOverlappedPipe(DWORD openMode, DWORD bufferSize)
|
||||
{
|
||||
LARGE_INTEGER timeout = { .QuadPart = -10'0000'0000 }; // 1 second
|
||||
UNICODE_STRING emptyPath{};
|
||||
IO_STATUS_BLOCK statusBlock;
|
||||
OBJECT_ATTRIBUTES objectAttributes{
|
||||
.Length = sizeof(OBJECT_ATTRIBUTES),
|
||||
.ObjectName = &emptyPath,
|
||||
.Attributes = OBJ_CASE_INSENSITIVE,
|
||||
};
|
||||
DWORD serverDesiredAccess = 0;
|
||||
DWORD clientDesiredAccess = 0;
|
||||
DWORD serverShareAccess = 0;
|
||||
DWORD clientShareAccess = 0;
|
||||
|
||||
switch (openMode)
|
||||
{
|
||||
case PIPE_ACCESS_INBOUND:
|
||||
serverDesiredAccess = SYNCHRONIZE | GENERIC_READ | FILE_WRITE_ATTRIBUTES;
|
||||
clientDesiredAccess = SYNCHRONIZE | GENERIC_WRITE | FILE_READ_ATTRIBUTES;
|
||||
serverShareAccess = FILE_SHARE_WRITE;
|
||||
clientShareAccess = FILE_SHARE_READ;
|
||||
break;
|
||||
case PIPE_ACCESS_OUTBOUND:
|
||||
serverDesiredAccess = SYNCHRONIZE | GENERIC_WRITE | FILE_READ_ATTRIBUTES;
|
||||
clientDesiredAccess = SYNCHRONIZE | GENERIC_READ | FILE_WRITE_ATTRIBUTES;
|
||||
serverShareAccess = FILE_SHARE_READ;
|
||||
clientShareAccess = FILE_SHARE_WRITE;
|
||||
break;
|
||||
case PIPE_ACCESS_DUPLEX:
|
||||
serverDesiredAccess = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE;
|
||||
clientDesiredAccess = SYNCHRONIZE | GENERIC_READ | GENERIC_WRITE;
|
||||
serverShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
||||
clientShareAccess = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
||||
break;
|
||||
default:
|
||||
THROW_HR(E_UNEXPECTED);
|
||||
}
|
||||
|
||||
// Cache a handle to the pipe driver.
|
||||
static const auto pipeDirectory = []() {
|
||||
UNICODE_STRING path = RTL_CONSTANT_STRING(L"\\Device\\NamedPipe\\");
|
||||
|
||||
OBJECT_ATTRIBUTES objectAttributes{
|
||||
.Length = sizeof(OBJECT_ATTRIBUTES),
|
||||
.ObjectName = &path,
|
||||
};
|
||||
|
||||
wil::unique_hfile dir;
|
||||
IO_STATUS_BLOCK statusBlock;
|
||||
THROW_IF_NTSTATUS_FAILED(NtCreateFile(
|
||||
/* FileHandle */ dir.addressof(),
|
||||
/* DesiredAccess */ SYNCHRONIZE | GENERIC_READ,
|
||||
/* ObjectAttributes */ &objectAttributes,
|
||||
/* IoStatusBlock */ &statusBlock,
|
||||
/* AllocationSize */ nullptr,
|
||||
/* FileAttributes */ 0,
|
||||
/* ShareAccess */ FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
/* CreateDisposition */ FILE_OPEN,
|
||||
/* CreateOptions */ FILE_SYNCHRONOUS_IO_NONALERT,
|
||||
/* EaBuffer */ nullptr,
|
||||
/* EaLength */ 0));
|
||||
|
||||
return dir;
|
||||
}();
|
||||
|
||||
wil::unique_hfile server;
|
||||
objectAttributes.RootDirectory = pipeDirectory.get();
|
||||
THROW_IF_NTSTATUS_FAILED(NtCreateNamedPipeFile(
|
||||
/* FileHandle */ server.addressof(),
|
||||
/* DesiredAccess */ serverDesiredAccess,
|
||||
/* ObjectAttributes */ &objectAttributes,
|
||||
/* IoStatusBlock */ &statusBlock,
|
||||
/* ShareAccess */ serverShareAccess,
|
||||
/* CreateDisposition */ FILE_CREATE,
|
||||
/* CreateOptions */ 0, // would be FILE_SYNCHRONOUS_IO_NONALERT for a synchronous pipe
|
||||
/* NamedPipeType */ FILE_PIPE_BYTE_STREAM_TYPE,
|
||||
/* ReadMode */ FILE_PIPE_BYTE_STREAM_MODE,
|
||||
/* CompletionMode */ FILE_PIPE_QUEUE_OPERATION, // would be FILE_PIPE_COMPLETE_OPERATION for PIPE_NOWAIT
|
||||
/* MaximumInstances */ 1,
|
||||
/* InboundQuota */ bufferSize,
|
||||
/* OutboundQuota */ bufferSize,
|
||||
/* DefaultTimeout */ &timeout));
|
||||
|
||||
wil::unique_hfile client;
|
||||
objectAttributes.RootDirectory = server.get();
|
||||
THROW_IF_NTSTATUS_FAILED(NtCreateFile(
|
||||
/* FileHandle */ client.addressof(),
|
||||
/* DesiredAccess */ clientDesiredAccess,
|
||||
/* ObjectAttributes */ &objectAttributes,
|
||||
/* IoStatusBlock */ &statusBlock,
|
||||
/* AllocationSize */ nullptr,
|
||||
/* FileAttributes */ 0,
|
||||
/* ShareAccess */ clientShareAccess,
|
||||
/* CreateDisposition */ FILE_OPEN,
|
||||
/* CreateOptions */ FILE_NON_DIRECTORY_FILE, // would include FILE_SYNCHRONOUS_IO_NONALERT for a synchronous pipe
|
||||
/* EaBuffer */ nullptr,
|
||||
/* EaLength */ 0));
|
||||
|
||||
return { std::move(server), std::move(client) };
|
||||
}
|
||||
|
||||
// GetOverlappedResult() for professionals! Only for single-threaded use.
|
||||
//
|
||||
// GetOverlappedResult() used to have a neat optimization where it would only call WaitForSingleObject() if the state was STATUS_PENDING.
|
||||
// That got removed in Windows 7, because people kept starting a read/write on one thread and called GetOverlappedResult() on another.
|
||||
// When the OS sets Internal from STATUS_PENDING to 0 (= done) and then flags the hEvent, that doesn't happen atomically.
|
||||
// This results in a race condition if a OVERLAPPED is used across threads.
|
||||
HRESULT Utils::GetOverlappedResultSameThread(const OVERLAPPED* overlapped, DWORD* bytesTransferred) noexcept
|
||||
{
|
||||
assert(overlapped != nullptr);
|
||||
assert(overlapped->hEvent != nullptr);
|
||||
assert(bytesTransferred != nullptr);
|
||||
|
||||
__assume(overlapped != nullptr);
|
||||
__assume(overlapped->hEvent != nullptr);
|
||||
__assume(bytesTransferred != nullptr);
|
||||
|
||||
if (overlapped->Internal == STATUS_PENDING)
|
||||
{
|
||||
if (WaitForSingleObjectEx(overlapped->hEvent, INFINITE, FALSE) != WAIT_OBJECT_0)
|
||||
{
|
||||
return HRESULT_FROM_WIN32(GetLastError());
|
||||
}
|
||||
}
|
||||
|
||||
// Assuming no multi-threading as per the function contract and
|
||||
// now that we ensured that hEvent is set (= read/write done),
|
||||
// we can safely read whatever want because nothing will set these concurrently.
|
||||
*bytesTransferred = gsl::narrow_cast<DWORD>(overlapped->InternalHigh);
|
||||
return HRESULT_FROM_NT(overlapped->Internal);
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Generate a Version 5 UUID (specified in RFC4122 4.3)
|
||||
// v5 UUIDs are stable given the same namespace and "name".
|
||||
|
|
Loading…
Reference in New Issue