mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
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
1
.github/actions/spelling/expect/expect.txt
vendored
1
.github/actions/spelling/expect/expect.txt
vendored
@ -1205,6 +1205,7 @@ nouicompat
|
|||||||
nounihan
|
nounihan
|
||||||
NOYIELD
|
NOYIELD
|
||||||
NOZORDER
|
NOZORDER
|
||||||
|
NPFS
|
||||||
nrcs
|
nrcs
|
||||||
NSTATUS
|
NSTATUS
|
||||||
ntapi
|
ntapi
|
||||||
|
|||||||
@ -15,6 +15,12 @@ Author(s):
|
|||||||
|
|
||||||
namespace Microsoft::Console::Utils
|
namespace Microsoft::Console::Utils
|
||||||
{
|
{
|
||||||
|
struct Pipe
|
||||||
|
{
|
||||||
|
wil::unique_hfile server;
|
||||||
|
wil::unique_hfile client;
|
||||||
|
};
|
||||||
|
|
||||||
// Function Description:
|
// Function Description:
|
||||||
// - Returns -1, 0 or +1 to indicate the sign of the passed-in value.
|
// - Returns -1, 0 or +1 to indicate the sign of the passed-in value.
|
||||||
template<typename T>
|
template<typename T>
|
||||||
@ -24,6 +30,10 @@ namespace Microsoft::Console::Utils
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidHandle(const HANDLE handle) noexcept;
|
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:
|
// Function Description:
|
||||||
// - Clamps a long in between `min` and `SHRT_MAX`
|
// - Clamps a long in between `min` and `SHRT_MAX`
|
||||||
|
|||||||
@ -4,13 +4,11 @@
|
|||||||
#include "precomp.h"
|
#include "precomp.h"
|
||||||
#include "inc/utils.hpp"
|
#include "inc/utils.hpp"
|
||||||
|
|
||||||
#include <propsys.h>
|
#include <til/string.h>
|
||||||
|
#include <wil/token_helpers.h>
|
||||||
|
|
||||||
#include "inc/colorTable.hpp"
|
#include "inc/colorTable.hpp"
|
||||||
|
|
||||||
#include <wil/token_helpers.h>
|
|
||||||
#include <til/string.h>
|
|
||||||
|
|
||||||
using namespace Microsoft::Console;
|
using namespace Microsoft::Console;
|
||||||
|
|
||||||
// Routine Description:
|
// Routine Description:
|
||||||
@ -631,6 +629,208 @@ bool Utils::IsValidHandle(const HANDLE handle) noexcept
|
|||||||
return handle != nullptr && handle != INVALID_HANDLE_VALUE;
|
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:
|
// Function Description:
|
||||||
// - Generate a Version 5 UUID (specified in RFC4122 4.3)
|
// - Generate a Version 5 UUID (specified in RFC4122 4.3)
|
||||||
// v5 UUIDs are stable given the same namespace and "name".
|
// v5 UUIDs are stable given the same namespace and "name".
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user