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:
parent
6c5a5aa670
commit
fb7a19c4f1
@ -51,7 +51,7 @@ COMMON_OBJECTS = \
|
|||||||
build/winpty_wcsnlen.o
|
build/winpty_wcsnlen.o
|
||||||
|
|
||||||
WORKER_OBJECTS = build/WorkerProgram.o
|
WORKER_OBJECTS = build/WorkerProgram.o
|
||||||
TEST_OBJECTS = build/TestCommon.o
|
TEST_OBJECTS = build/RemoteHandle.o build/RemoteWorker.o
|
||||||
|
|
||||||
all : \
|
all : \
|
||||||
build/Test_GetConsoleTitleW.exe \
|
build/Test_GetConsoleTitleW.exe \
|
||||||
|
@ -4,13 +4,7 @@
|
|||||||
// Windows 10, inclusive.
|
// Windows 10, inclusive.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <cassert>
|
|
||||||
#include <iostream>
|
|
||||||
#include <tuple>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <TestCommon.h>
|
#include <TestCommon.h>
|
||||||
#include <OsVersion.h>
|
|
||||||
|
|
||||||
#define CHECK(cond) \
|
#define CHECK(cond) \
|
||||||
do { \
|
do { \
|
||||||
|
@ -6,15 +6,7 @@
|
|||||||
// * Windows 8 and up (at least to Windows 10)
|
// * Windows 8 and up (at least to Windows 10)
|
||||||
//
|
//
|
||||||
|
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
|
||||||
#include <cwchar>
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
#include <OsVersion.h>
|
|
||||||
#include <TestCommon.h>
|
#include <TestCommon.h>
|
||||||
#include <UnicodeConversions.h>
|
|
||||||
#include <winpty_wcsnlen.h>
|
|
||||||
|
|
||||||
#define CHECK_EQ(actual, expected) \
|
#define CHECK_EQ(actual, expected) \
|
||||||
do { \
|
do { \
|
||||||
|
178
misc/buffer-tests/harness/RemoteHandle.cc
Executable file
178
misc/buffer-tests/harness/RemoteHandle.cc
Executable 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, ®ion, 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, ®ion, 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;
|
||||||
|
}
|
||||||
|
|
54
misc/buffer-tests/harness/RemoteHandle.h
Executable file
54
misc/buffer-tests/harness/RemoteHandle.h
Executable 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;
|
||||||
|
};
|
154
misc/buffer-tests/harness/RemoteWorker.cc
Executable file
154
misc/buffer-tests/harness/RemoteWorker.cc
Executable 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();
|
||||||
|
}
|
111
misc/buffer-tests/harness/RemoteWorker.h
Executable file
111
misc/buffer-tests/harness/RemoteWorker.h
Executable 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;
|
||||||
|
};
|
@ -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, ®ion, 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, ®ion, 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();
|
|
||||||
}
|
|
@ -1,151 +1,29 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <windows.h>
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <memory>
|
||||||
|
#include <cwchar>
|
||||||
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <tuple>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include "Command.h"
|
#include "NtHandleQuery.h"
|
||||||
#include "Event.h"
|
#include "OsVersion.h"
|
||||||
#include "ShmemParcel.h"
|
#include "RemoteHandle.h"
|
||||||
#include "Spawn.h"
|
#include "RemoteWorker.h"
|
||||||
#include "UnicodeConversions.h"
|
#include "UnicodeConversions.h"
|
||||||
|
|
||||||
#include <DebugClient.h>
|
#include <DebugClient.h>
|
||||||
|
#include <WinptyAssert.h>
|
||||||
|
#include <winpty_wcsnlen.h>
|
||||||
|
|
||||||
class Worker;
|
using Handle = RemoteHandle;
|
||||||
|
using Worker = RemoteWorker;
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
@ -8,11 +8,14 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <cassert>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <cwchar>
|
||||||
|
#include <iostream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
|
Loading…
Reference in New Issue
Block a user