Split TestCommon -> Remote{Worker,Handle} and add TestCommon.h uber-header

* TestCommon.h is a convenience header for the test cases.  It aliases
   Remote{Worker,Handle} back to {Worker,Handle}, and it includes many
   basic C/C++ headers, as well as the parts of the harness suitable for
   test cases.
This commit is contained in:
Ryan Prichard 2015-10-22 18:16:47 -05:00
parent 6c5a5aa670
commit fb7a19c4f1
10 changed files with 520 additions and 473 deletions

View File

@ -51,7 +51,7 @@ COMMON_OBJECTS = \
build/winpty_wcsnlen.o
WORKER_OBJECTS = build/WorkerProgram.o
TEST_OBJECTS = build/TestCommon.o
TEST_OBJECTS = build/RemoteHandle.o build/RemoteWorker.o
all : \
build/Test_GetConsoleTitleW.exe \

View File

@ -4,13 +4,7 @@
// Windows 10, inclusive.
//
#include <cassert>
#include <iostream>
#include <tuple>
#include <vector>
#include <TestCommon.h>
#include <OsVersion.h>
#define CHECK(cond) \
do { \

View File

@ -6,15 +6,7 @@
// * Windows 8 and up (at least to Windows 10)
//
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <iostream>
#include <OsVersion.h>
#include <TestCommon.h>
#include <UnicodeConversions.h>
#include <winpty_wcsnlen.h>
#define CHECK_EQ(actual, expected) \
do { \

View File

@ -0,0 +1,178 @@
#include "RemoteHandle.h"
#include <string>
#include "RemoteWorker.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
RemoteHandle RemoteHandle::dup(HANDLE h, RemoteWorker &target,
BOOL bInheritHandle) {
HANDLE targetHandle;
BOOL success = DuplicateHandle(
GetCurrentProcess(),
h,
target.m_process,
&targetHandle,
0, bInheritHandle, DUPLICATE_SAME_ACCESS);
ASSERT(success && "DuplicateHandle failed");
return RemoteHandle(targetHandle, target);
}
RemoteHandle &RemoteHandle::activate() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetActiveBuffer);
return *this;
}
void RemoteHandle::write(const std::string &msg) {
worker().cmd().handle = m_value;
worker().cmd().u.writeText = msg;
worker().rpc(Command::WriteText);
}
void RemoteHandle::close() {
worker().cmd().handle = m_value;
worker().rpc(Command::Close);
}
RemoteHandle &RemoteHandle::setStdin() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdin);
return *this;
}
RemoteHandle &RemoteHandle::setStdout() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdout);
return *this;
}
RemoteHandle &RemoteHandle::setStderr() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStderr);
return *this;
}
RemoteHandle RemoteHandle::dup(RemoteWorker &target, BOOL bInheritHandle) {
HANDLE targetProcessFromSource;
if (&target == &worker()) {
targetProcessFromSource = GetCurrentProcess();
} else {
// Allow the source worker to see the target worker.
targetProcessFromSource = INVALID_HANDLE_VALUE;
BOOL success = DuplicateHandle(
GetCurrentProcess(),
target.m_process,
worker().m_process,
&targetProcessFromSource,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(success && "Process handle duplication failed");
}
// Do the user-level duplication in the source process.
worker().cmd().handle = m_value;
worker().cmd().targetProcess = targetProcessFromSource;
worker().cmd().bInheritHandle = bInheritHandle;
worker().rpc(Command::Duplicate);
HANDLE retHandle = worker().cmd().handle;
if (&target != &worker()) {
// Cleanup targetProcessFromSource.
worker().cmd().handle = targetProcessFromSource;
worker().rpc(Command::CloseQuietly);
ASSERT(worker().cmd().success &&
"Error closing remote process handle");
}
return RemoteHandle(retHandle, target);
}
CONSOLE_SCREEN_BUFFER_INFO RemoteHandle::screenBufferInfo() {
CONSOLE_SCREEN_BUFFER_INFO ret;
bool success = tryScreenBufferInfo(&ret);
ASSERT(success && "GetConsoleScreenBufferInfo failed");
return ret;
}
bool RemoteHandle::tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetConsoleScreenBufferInfo);
if (worker().cmd().success && info != nullptr) {
*info = worker().cmd().u.consoleScreenBufferInfo;
}
return worker().cmd().success;
}
DWORD RemoteHandle::flags() {
DWORD ret;
bool success = tryFlags(&ret);
ASSERT(success && "GetHandleInformation failed");
return ret;
}
bool RemoteHandle::tryFlags(DWORD *flags) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetHandleInformation);
if (worker().cmd().success && flags != nullptr) {
*flags = worker().cmd().dword;
}
return worker().cmd().success;
}
void RemoteHandle::setFlags(DWORD mask, DWORD flags) {
bool success = trySetFlags(mask, flags);
ASSERT(success && "SetHandleInformation failed");
}
bool RemoteHandle::trySetFlags(DWORD mask, DWORD flags) {
worker().cmd().handle = m_value;
worker().cmd().u.setFlags.mask = mask;
worker().cmd().u.setFlags.flags = flags;
worker().rpc(Command::SetHandleInformation);
return worker().cmd().success;
}
wchar_t RemoteHandle::firstChar() {
// The "first char" is useful for identifying which output buffer a handle
// refers to.
worker().cmd().handle = m_value;
const SMALL_RECT region = {};
auto &io = worker().cmd().u.consoleIo;
io.bufferSize = { 1, 1 };
io.bufferCoord = {};
io.ioRegion = region;
worker().rpc(Command::ReadConsoleOutput);
ASSERT(worker().cmd().success);
ASSERT(!memcmp(&io.ioRegion, &region, sizeof(region)));
return io.buffer[0].Char.UnicodeChar;
}
RemoteHandle &RemoteHandle::setFirstChar(wchar_t ch) {
// The "first char" is useful for identifying which output buffer a handle
// refers to.
worker().cmd().handle = m_value;
const SMALL_RECT region = {};
auto &io = worker().cmd().u.consoleIo;
io.buffer[0].Char.UnicodeChar = ch;
io.buffer[0].Attributes = 7;
io.bufferSize = { 1, 1 };
io.bufferCoord = {};
io.ioRegion = region;
worker().rpc(Command::WriteConsoleOutput);
ASSERT(worker().cmd().success);
ASSERT(!memcmp(&io.ioRegion, &region, sizeof(region)));
return *this;
}
bool RemoteHandle::tryNumberOfConsoleInputEvents(DWORD *ret) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetNumberOfConsoleInputEvents);
if (worker().cmd().success && ret != nullptr) {
*ret = worker().cmd().dword;
}
return worker().cmd().success;
}

View File

@ -0,0 +1,54 @@
#pragma once
#include <windows.h>
#include <cstdint>
#include <string>
class RemoteWorker;
class RemoteHandle {
friend class RemoteWorker;
private:
RemoteHandle(HANDLE value, RemoteWorker &worker) :
m_value(value), m_worker(&worker)
{
}
public:
static RemoteHandle invent(HANDLE h, RemoteWorker &worker) {
return RemoteHandle(h, worker);
}
static RemoteHandle invent(uint64_t h, RemoteWorker &worker) {
return RemoteHandle(reinterpret_cast<HANDLE>(h), worker);
}
RemoteHandle &activate();
void write(const std::string &msg);
void close();
RemoteHandle &setStdin();
RemoteHandle &setStdout();
RemoteHandle &setStderr();
RemoteHandle dup(RemoteWorker &target, BOOL bInheritHandle=FALSE);
RemoteHandle dup(BOOL bInheritHandle=FALSE) {
return dup(worker(), bInheritHandle);
}
static RemoteHandle dup(HANDLE h, RemoteWorker &target,
BOOL bInheritHandle=FALSE);
CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo();
bool tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info=nullptr);
DWORD flags();
bool tryFlags(DWORD *flags=nullptr);
void setFlags(DWORD mask, DWORD flags);
bool trySetFlags(DWORD mask, DWORD flags);
wchar_t firstChar();
RemoteHandle &setFirstChar(wchar_t ch);
bool tryNumberOfConsoleInputEvents(DWORD *ret=nullptr);
HANDLE value() const { return m_value; }
uint64_t uvalue() const { return reinterpret_cast<uint64_t>(m_value); }
RemoteWorker &worker() const { return *m_worker; }
private:
HANDLE m_value;
RemoteWorker *m_worker;
};

View File

@ -0,0 +1,154 @@
#include "RemoteWorker.h"
#include <string>
#include "UnicodeConversions.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
namespace {
static std::string timeString() {
FILETIME fileTime;
GetSystemTimeAsFileTime(&fileTime);
auto ret = ((uint64_t)fileTime.dwHighDateTime << 32) |
fileTime.dwLowDateTime;
return std::to_string(ret);
}
static std::string newWorkerName() {
static int workerCounter = 0;
static auto initialTimeString = timeString();
return std::string("WinptyBufferTests-") +
std::to_string(static_cast<int>(GetCurrentProcessId())) + "-" +
initialTimeString + "-" +
std::to_string(++workerCounter);
}
} // anonymous namespace
RemoteWorker::RemoteWorker(const std::string &name) :
m_name(name),
m_parcel(name + "-shmem", ShmemParcel::CreateNew),
m_startEvent(name + "-start"),
m_finishEvent(name + "-finish")
{
m_finishEvent.set();
}
RemoteWorker::RemoteWorker(SpawnParams params) :
RemoteWorker(newWorkerName()) {
m_process = spawn(m_name, params);
// Perform an RPC just to ensure that the worker process is ready, and
// the console exists, before returning.
rpc(Command::GetStdin);
}
RemoteWorker RemoteWorker::child(SpawnParams params) {
RemoteWorker ret(newWorkerName());
cmd().u.spawn.spawnName = ret.m_name;
cmd().u.spawn.spawnParams = params;
rpc(Command::SpawnChild);
BOOL dupSuccess = DuplicateHandle(
m_process,
cmd().handle,
GetCurrentProcess(),
&ret.m_process,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(dupSuccess && "RemoteWorker::child: DuplicateHandle failed");
rpc(Command::CloseQuietly);
ASSERT(cmd().success && "RemoteWorker::child: CloseHandle failed");
// Perform an RPC just to ensure that the worker process is ready, and
// the console exists, before returning.
ret.rpc(Command::GetStdin);
return ret;
}
RemoteWorker::~RemoteWorker() {
cleanup();
}
void RemoteWorker::cleanup() {
if (m_valid) {
cmd().dword = 0;
rpcAsync(Command::Exit);
DWORD result = WaitForSingleObject(m_process, INFINITE);
ASSERT(result == WAIT_OBJECT_0 &&
"WaitForSingleObject failed while killing worker");
CloseHandle(m_process);
m_valid = false;
}
}
CONSOLE_SELECTION_INFO RemoteWorker::selectionInfo() {
rpc(Command::GetConsoleSelectionInfo);
ASSERT(cmd().success);
return cmd().u.consoleSelectionInfo;
}
void RemoteWorker::dumpConsoleHandles(BOOL writeToEach) {
cmd().writeToEach = writeToEach;
rpc(Command::DumpConsoleHandles);
}
std::vector<RemoteHandle> RemoteWorker::scanForConsoleHandles() {
rpc(Command::ScanForConsoleHandles);
auto &rpcTable = cmd().u.scanForConsoleHandles;
std::vector<RemoteHandle> ret;
for (int i = 0; i < rpcTable.count; ++i) {
ret.push_back(RemoteHandle(rpcTable.table[i], *this));
}
return ret;
}
bool RemoteWorker::setTitleInternal(const std::wstring &wstr) {
ASSERT(wstr.size() < cmd().u.consoleTitle.size());
ASSERT(wstr.size() == wcslen(wstr.c_str()));
wcscpy(cmd().u.consoleTitle.data(), wstr.c_str());
rpc(Command::SetConsoleTitle);
return cmd().success;
}
std::string RemoteWorker::title() {
std::array<wchar_t, 1024> buf;
DWORD ret = titleInternal(buf, buf.size());
ret = std::min<DWORD>(ret, buf.size() - 1);
buf[std::min<size_t>(buf.size() - 1, ret)] = L'\0';
return narrowString(std::wstring(buf.data()));
}
// This API is more low-level than typical, because GetConsoleTitleW is buggy
// in older versions of Windows, and this method is used to test the bugs.
DWORD RemoteWorker::titleInternal(std::array<wchar_t, 1024> &buf, DWORD bufSize) {
cmd().dword = bufSize;
cmd().u.consoleTitle = buf;
rpc(Command::GetConsoleTitle);
buf = cmd().u.consoleTitle;
return cmd().dword;
}
std::vector<DWORD> RemoteWorker::consoleProcessList() {
rpc(Command::GetConsoleProcessList);
DWORD count = cmd().dword;
ASSERT(count <= cmd().u.processList.size());
return std::vector<DWORD>(
&cmd().u.processList[0],
&cmd().u.processList[count]);
}
void RemoteWorker::rpc(Command::Kind kind) {
rpcImpl(kind);
m_finishEvent.wait();
}
void RemoteWorker::rpcAsync(Command::Kind kind) {
rpcImpl(kind);
}
void RemoteWorker::rpcImpl(Command::Kind kind) {
m_finishEvent.wait();
m_finishEvent.reset();
cmd().kind = kind;
m_startEvent.set();
}

View File

@ -0,0 +1,111 @@
#pragma once
#include <windows.h>
#include <string>
#include <vector>
#include "Command.h"
#include "Event.h"
#include "RemoteHandle.h"
#include "ShmemParcel.h"
#include "Spawn.h"
#include "UnicodeConversions.h"
class RemoteWorker {
friend class RemoteHandle;
private:
RemoteWorker(const std::string &name);
public:
RemoteWorker() : RemoteWorker(SpawnParams { false, CREATE_NEW_CONSOLE }) {}
RemoteWorker(SpawnParams params);
RemoteWorker child() { return child(SpawnParams {}); }
RemoteWorker child(SpawnParams params);
~RemoteWorker();
private:
void cleanup();
public:
// basic worker info
HANDLE processHandle() { return m_process; }
DWORD pid() { return GetProcessId(m_process); }
// allow moving
RemoteWorker(RemoteWorker &&other) :
m_name(std::move(other.m_name)),
m_parcel(std::move(other.m_parcel)),
m_startEvent(std::move(other.m_startEvent)),
m_finishEvent(std::move(other.m_finishEvent)),
m_process(std::move(other.m_process))
{
other.m_valid = false;
}
RemoteWorker &operator=(RemoteWorker &&other) {
cleanup();
m_name = std::move(other.m_name);
m_parcel = std::move(other.m_parcel);
m_startEvent = std::move(other.m_startEvent);
m_finishEvent = std::move(other.m_finishEvent);
m_process = std::move(other.m_process);
other.m_valid = false;
m_valid = true;
return *this;
}
// Commands
RemoteHandle getStdin() { rpc(Command::GetStdin); return RemoteHandle(cmd().handle, *this); }
RemoteHandle getStdout() { rpc(Command::GetStdout); return RemoteHandle(cmd().handle, *this); }
RemoteHandle getStderr() { rpc(Command::GetStderr); return RemoteHandle(cmd().handle, *this); }
bool detach() { rpc(Command::FreeConsole); return cmd().success; }
bool attach(RemoteWorker &worker) { cmd().dword = GetProcessId(worker.m_process); rpc(Command::AttachConsole); return cmd().success; }
bool alloc() { rpc(Command::AllocConsole); return cmd().success; }
void dumpStandardHandles() { rpc(Command::DumpStandardHandles); }
int system(const std::string &arg) { cmd().u.systemText = arg; rpc(Command::System); return cmd().dword; }
HWND consoleWindow() { rpc(Command::GetConsoleWindow); return cmd().hwnd; }
CONSOLE_SELECTION_INFO selectionInfo();
void dumpConsoleHandles(BOOL writeToEach=FALSE);
std::vector<RemoteHandle> scanForConsoleHandles();
void setTitle(const std::string &str) { auto b = setTitleInternal(widenString(str)); ASSERT(b && "setTitle failed"); }
bool setTitleInternal(const std::wstring &str);
std::string title();
DWORD titleInternal(std::array<wchar_t, 1024> &buf, DWORD bufSize);
std::vector<DWORD> consoleProcessList();
RemoteHandle openConin(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::OpenConin);
return RemoteHandle(cmd().handle, *this);
}
RemoteHandle openConout(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::OpenConout);
return RemoteHandle(cmd().handle, *this);
}
RemoteHandle newBuffer(BOOL bInheritHandle=FALSE, wchar_t firstChar=L'\0') {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::NewBuffer);
auto h = RemoteHandle(cmd().handle, *this);
if (firstChar != L'\0') {
h.setFirstChar(firstChar);
}
return h;
}
private:
Command &cmd() { return m_parcel.value(); }
void rpc(Command::Kind kind);
void rpcAsync(Command::Kind kind);
void rpcImpl(Command::Kind kind);
private:
bool m_valid = true;
std::string m_name;
ShmemParcelTyped<Command> m_parcel;
Event m_startEvent;
Event m_finishEvent;
HANDLE m_process = NULL;
};

View File

@ -1,317 +0,0 @@
#include "TestCommon.h"
#include <string>
#include "UnicodeConversions.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
Handle Handle::dup(HANDLE h, Worker &target, BOOL bInheritHandle) {
HANDLE targetHandle;
BOOL success = DuplicateHandle(
GetCurrentProcess(),
h,
target.m_process,
&targetHandle,
0, bInheritHandle, DUPLICATE_SAME_ACCESS);
ASSERT(success && "DuplicateHandle failed");
return Handle(targetHandle, target);
}
Handle &Handle::activate() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetActiveBuffer);
return *this;
}
void Handle::write(const std::string &msg) {
worker().cmd().handle = m_value;
worker().cmd().u.writeText = msg;
worker().rpc(Command::WriteText);
}
void Handle::close() {
worker().cmd().handle = m_value;
worker().rpc(Command::Close);
}
Handle &Handle::setStdin() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdin);
return *this;
}
Handle &Handle::setStdout() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStdout);
return *this;
}
Handle &Handle::setStderr() {
worker().cmd().handle = m_value;
worker().rpc(Command::SetStderr);
return *this;
}
Handle Handle::dup(Worker &target, BOOL bInheritHandle) {
HANDLE targetProcessFromSource;
if (&target == &worker()) {
targetProcessFromSource = GetCurrentProcess();
} else {
// Allow the source worker to see the target worker.
targetProcessFromSource = INVALID_HANDLE_VALUE;
BOOL success = DuplicateHandle(
GetCurrentProcess(),
target.m_process,
worker().m_process,
&targetProcessFromSource,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(success && "Process handle duplication failed");
}
// Do the user-level duplication in the source process.
worker().cmd().handle = m_value;
worker().cmd().targetProcess = targetProcessFromSource;
worker().cmd().bInheritHandle = bInheritHandle;
worker().rpc(Command::Duplicate);
HANDLE retHandle = worker().cmd().handle;
if (&target != &worker()) {
// Cleanup targetProcessFromSource.
worker().cmd().handle = targetProcessFromSource;
worker().rpc(Command::CloseQuietly);
ASSERT(worker().cmd().success &&
"Error closing remote process handle");
}
return Handle(retHandle, target);
}
CONSOLE_SCREEN_BUFFER_INFO Handle::screenBufferInfo() {
CONSOLE_SCREEN_BUFFER_INFO ret;
bool success = tryScreenBufferInfo(&ret);
ASSERT(success && "GetConsoleScreenBufferInfo failed");
return ret;
}
bool Handle::tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetConsoleScreenBufferInfo);
if (worker().cmd().success && info != nullptr) {
*info = worker().cmd().u.consoleScreenBufferInfo;
}
return worker().cmd().success;
}
DWORD Handle::flags() {
DWORD ret;
bool success = tryFlags(&ret);
ASSERT(success && "GetHandleInformation failed");
return ret;
}
bool Handle::tryFlags(DWORD *flags) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetHandleInformation);
if (worker().cmd().success && flags != nullptr) {
*flags = worker().cmd().dword;
}
return worker().cmd().success;
}
void Handle::setFlags(DWORD mask, DWORD flags) {
bool success = trySetFlags(mask, flags);
ASSERT(success && "SetHandleInformation failed");
}
bool Handle::trySetFlags(DWORD mask, DWORD flags) {
worker().cmd().handle = m_value;
worker().cmd().u.setFlags.mask = mask;
worker().cmd().u.setFlags.flags = flags;
worker().rpc(Command::SetHandleInformation);
return worker().cmd().success;
}
wchar_t Handle::firstChar() {
// The "first char" is useful for identifying which output buffer a handle
// refers to.
worker().cmd().handle = m_value;
const SMALL_RECT region = {};
auto &io = worker().cmd().u.consoleIo;
io.bufferSize = { 1, 1 };
io.bufferCoord = {};
io.ioRegion = region;
worker().rpc(Command::ReadConsoleOutput);
ASSERT(worker().cmd().success);
ASSERT(!memcmp(&io.ioRegion, &region, sizeof(region)));
return io.buffer[0].Char.UnicodeChar;
}
Handle &Handle::setFirstChar(wchar_t ch) {
// The "first char" is useful for identifying which output buffer a handle
// refers to.
worker().cmd().handle = m_value;
const SMALL_RECT region = {};
auto &io = worker().cmd().u.consoleIo;
io.buffer[0].Char.UnicodeChar = ch;
io.buffer[0].Attributes = 7;
io.bufferSize = { 1, 1 };
io.bufferCoord = {};
io.ioRegion = region;
worker().rpc(Command::WriteConsoleOutput);
ASSERT(worker().cmd().success);
ASSERT(!memcmp(&io.ioRegion, &region, sizeof(region)));
return *this;
}
bool Handle::tryNumberOfConsoleInputEvents(DWORD *ret) {
worker().cmd().handle = m_value;
worker().rpc(Command::GetNumberOfConsoleInputEvents);
if (worker().cmd().success && ret != nullptr) {
*ret = worker().cmd().dword;
}
return worker().cmd().success;
}
static std::string timeString() {
FILETIME fileTime;
GetSystemTimeAsFileTime(&fileTime);
auto ret = ((uint64_t)fileTime.dwHighDateTime << 32) |
fileTime.dwLowDateTime;
return std::to_string(ret);
}
static std::string newWorkerName() {
static int workerCounter = 0;
static auto initialTimeString = timeString();
return std::string("WinptyBufferTests-") +
std::to_string(static_cast<int>(GetCurrentProcessId())) + "-" +
initialTimeString + "-" +
std::to_string(++workerCounter);
}
Worker::Worker(const std::string &name) :
m_name(name),
m_parcel(name + "-shmem", ShmemParcel::CreateNew),
m_startEvent(name + "-start"),
m_finishEvent(name + "-finish")
{
m_finishEvent.set();
}
Worker::Worker(SpawnParams params) : Worker(newWorkerName()) {
m_process = spawn(m_name, params);
// Perform an RPC just to ensure that the worker process is ready, and
// the console exists, before returning.
rpc(Command::GetStdin);
}
Worker Worker::child(SpawnParams params) {
Worker ret(newWorkerName());
cmd().u.spawn.spawnName = ret.m_name;
cmd().u.spawn.spawnParams = params;
rpc(Command::SpawnChild);
BOOL dupSuccess = DuplicateHandle(
m_process,
cmd().handle,
GetCurrentProcess(),
&ret.m_process,
0, FALSE, DUPLICATE_SAME_ACCESS);
ASSERT(dupSuccess && "Worker::child: DuplicateHandle failed");
rpc(Command::CloseQuietly);
ASSERT(cmd().success && "Worker::child: CloseHandle failed");
// Perform an RPC just to ensure that the worker process is ready, and
// the console exists, before returning.
ret.rpc(Command::GetStdin);
return ret;
}
Worker::~Worker() {
cleanup();
}
void Worker::cleanup() {
if (m_valid) {
cmd().dword = 0;
rpcAsync(Command::Exit);
DWORD result = WaitForSingleObject(m_process, INFINITE);
ASSERT(result == WAIT_OBJECT_0 &&
"WaitForSingleObject failed while killing worker");
CloseHandle(m_process);
m_valid = false;
}
}
CONSOLE_SELECTION_INFO Worker::selectionInfo() {
rpc(Command::GetConsoleSelectionInfo);
ASSERT(cmd().success);
return cmd().u.consoleSelectionInfo;
}
void Worker::dumpConsoleHandles(BOOL writeToEach) {
cmd().writeToEach = writeToEach;
rpc(Command::DumpConsoleHandles);
}
std::vector<Handle> Worker::scanForConsoleHandles() {
rpc(Command::ScanForConsoleHandles);
auto &rpcTable = cmd().u.scanForConsoleHandles;
std::vector<Handle> ret;
for (int i = 0; i < rpcTable.count; ++i) {
ret.push_back(Handle(rpcTable.table[i], *this));
}
return ret;
}
bool Worker::setTitleInternal(const std::wstring &wstr) {
ASSERT(wstr.size() < cmd().u.consoleTitle.size());
ASSERT(wstr.size() == wcslen(wstr.c_str()));
wcscpy(cmd().u.consoleTitle.data(), wstr.c_str());
rpc(Command::SetConsoleTitle);
return cmd().success;
}
std::string Worker::title() {
std::array<wchar_t, 1024> buf;
DWORD ret = titleInternal(buf, buf.size());
ret = std::min<DWORD>(ret, buf.size() - 1);
buf[std::min<size_t>(buf.size() - 1, ret)] = L'\0';
return narrowString(std::wstring(buf.data()));
}
// This API is more low-level than typical, because GetConsoleTitleW is buggy
// in older versions of Windows, and this method is used to test the bugs.
DWORD Worker::titleInternal(std::array<wchar_t, 1024> &buf, DWORD bufSize) {
cmd().dword = bufSize;
cmd().u.consoleTitle = buf;
rpc(Command::GetConsoleTitle);
buf = cmd().u.consoleTitle;
return cmd().dword;
}
std::vector<DWORD> Worker::consoleProcessList() {
rpc(Command::GetConsoleProcessList);
DWORD count = cmd().dword;
ASSERT(count <= cmd().u.processList.size());
return std::vector<DWORD>(
&cmd().u.processList[0],
&cmd().u.processList[count]);
}
void Worker::rpc(Command::Kind kind) {
rpcImpl(kind);
m_finishEvent.wait();
}
void Worker::rpcAsync(Command::Kind kind) {
rpcImpl(kind);
}
void Worker::rpcImpl(Command::Kind kind) {
m_finishEvent.wait();
m_finishEvent.reset();
cmd().kind = kind;
m_startEvent.set();
}

View File

@ -1,151 +1,29 @@
#pragma once
#include <windows.h>
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <cwchar>
#include <iostream>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
#include "Command.h"
#include "Event.h"
#include "ShmemParcel.h"
#include "Spawn.h"
#include "NtHandleQuery.h"
#include "OsVersion.h"
#include "RemoteHandle.h"
#include "RemoteWorker.h"
#include "UnicodeConversions.h"
#include <DebugClient.h>
#include <WinptyAssert.h>
#include <winpty_wcsnlen.h>
class Worker;
class Handle {
friend class Worker;
private:
Handle(HANDLE value, Worker &worker) : m_value(value), m_worker(&worker) {}
public:
static Handle invent(HANDLE h, Worker &worker) { return Handle(h, worker); }
static Handle invent(uint64_t h, Worker &worker) { return Handle(reinterpret_cast<HANDLE>(h), worker); }
Handle &activate();
void write(const std::string &msg);
void close();
Handle &setStdin();
Handle &setStdout();
Handle &setStderr();
Handle dup(Worker &target, BOOL bInheritHandle=FALSE);
Handle dup(BOOL bInheritHandle=FALSE) { return dup(worker(), bInheritHandle); }
static Handle dup(HANDLE h, Worker &target, BOOL bInheritHandle=FALSE);
CONSOLE_SCREEN_BUFFER_INFO screenBufferInfo();
bool tryScreenBufferInfo(CONSOLE_SCREEN_BUFFER_INFO *info=nullptr);
DWORD flags();
bool tryFlags(DWORD *flags=nullptr);
void setFlags(DWORD mask, DWORD flags);
bool trySetFlags(DWORD mask, DWORD flags);
wchar_t firstChar();
Handle &setFirstChar(wchar_t ch);
bool tryNumberOfConsoleInputEvents(DWORD *ret=nullptr);
HANDLE value() const { return m_value; }
uint64_t uvalue() const { return reinterpret_cast<uint64_t>(m_value); }
Worker &worker() const { return *m_worker; }
private:
HANDLE m_value;
Worker *m_worker;
};
class Worker {
friend class Handle;
private:
Worker(const std::string &name);
public:
Worker() : Worker(SpawnParams { false, CREATE_NEW_CONSOLE }) {}
Worker(SpawnParams params);
Worker child() { return child(SpawnParams {}); }
Worker child(SpawnParams params);
~Worker();
private:
void cleanup();
public:
// basic worker info
HANDLE processHandle() { return m_process; }
DWORD pid() { return GetProcessId(m_process); }
// allow moving
Worker(Worker &&other) :
m_name(std::move(other.m_name)),
m_parcel(std::move(other.m_parcel)),
m_startEvent(std::move(other.m_startEvent)),
m_finishEvent(std::move(other.m_finishEvent)),
m_process(std::move(other.m_process))
{
other.m_valid = false;
}
Worker &operator=(Worker &&other) {
cleanup();
m_name = std::move(other.m_name);
m_parcel = std::move(other.m_parcel);
m_startEvent = std::move(other.m_startEvent);
m_finishEvent = std::move(other.m_finishEvent);
m_process = std::move(other.m_process);
other.m_valid = false;
m_valid = true;
return *this;
}
// Commands
Handle getStdin() { rpc(Command::GetStdin); return Handle(cmd().handle, *this); }
Handle getStdout() { rpc(Command::GetStdout); return Handle(cmd().handle, *this); }
Handle getStderr() { rpc(Command::GetStderr); return Handle(cmd().handle, *this); }
bool detach() { rpc(Command::FreeConsole); return cmd().success; }
bool attach(Worker &worker) { cmd().dword = GetProcessId(worker.m_process); rpc(Command::AttachConsole); return cmd().success; }
bool alloc() { rpc(Command::AllocConsole); return cmd().success; }
void dumpStandardHandles() { rpc(Command::DumpStandardHandles); }
int system(const std::string &arg) { cmd().u.systemText = arg; rpc(Command::System); return cmd().dword; }
HWND consoleWindow() { rpc(Command::GetConsoleWindow); return cmd().hwnd; }
CONSOLE_SELECTION_INFO selectionInfo();
void dumpConsoleHandles(BOOL writeToEach=FALSE);
std::vector<Handle> scanForConsoleHandles();
void setTitle(const std::string &str) { auto b = setTitleInternal(widenString(str)); ASSERT(b && "setTitle failed"); }
bool setTitleInternal(const std::wstring &str);
std::string title();
DWORD titleInternal(std::array<wchar_t, 1024> &buf, DWORD bufSize);
std::vector<DWORD> consoleProcessList();
Handle openConin(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::OpenConin);
return Handle(cmd().handle, *this);
}
Handle openConout(BOOL bInheritHandle=FALSE) {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::OpenConout);
return Handle(cmd().handle, *this);
}
Handle newBuffer(BOOL bInheritHandle=FALSE, wchar_t firstChar=L'\0') {
cmd().bInheritHandle = bInheritHandle;
rpc(Command::NewBuffer);
auto h = Handle(cmd().handle, *this);
if (firstChar != L'\0') {
h.setFirstChar(firstChar);
}
return h;
}
private:
Command &cmd() { return m_parcel.value(); }
void rpc(Command::Kind kind);
void rpcAsync(Command::Kind kind);
void rpcImpl(Command::Kind kind);
private:
bool m_valid = true;
std::string m_name;
ShmemParcelTyped<Command> m_parcel;
Event m_startEvent;
Event m_finishEvent;
HANDLE m_process = NULL;
};
using Handle = RemoteHandle;
using Worker = RemoteWorker;

View File

@ -8,11 +8,14 @@
#include <algorithm>
#include <array>
#include <cassert>
#include <cstdio>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <cwchar>
#include <iostream>
#include <string>
#include <tuple>
#include <utility>