|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#include <windows.h> |
|
|
|
#include <stdint.h> |
|
#include <stdio.h> |
|
#include <string.h> |
|
|
|
#include <limits> |
|
#include <string> |
|
#include <vector> |
|
|
|
#include "../include/winpty.h" |
|
|
|
#include "../shared/AgentMsg.h" |
|
#include "../shared/BackgroundDesktop.h" |
|
#include "../shared/Buffer.h" |
|
#include "../shared/DebugClient.h" |
|
#include "../shared/GenRandom.h" |
|
#include "../shared/OwnedHandle.h" |
|
#include "../shared/StringBuilder.h" |
|
#include "../shared/StringUtil.h" |
|
#include "../shared/WindowsSecurity.h" |
|
#include "../shared/WindowsVersion.h" |
|
#include "../shared/WinptyAssert.h" |
|
#include "../shared/WinptyException.h" |
|
#include "../shared/WinptyVersion.h" |
|
|
|
#include "AgentLocation.h" |
|
#include "LibWinptyException.h" |
|
#include "WinptyInternal.h" |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static const winpty_error_s kOutOfMemory = { |
|
WINPTY_ERROR_OUT_OF_MEMORY, |
|
L"Out of memory", |
|
nullptr |
|
}; |
|
|
|
static const winpty_error_s kBadRpcPacket = { |
|
WINPTY_ERROR_UNSPECIFIED, |
|
L"Bad RPC packet", |
|
nullptr |
|
}; |
|
|
|
static const winpty_error_s kUncaughtException = { |
|
WINPTY_ERROR_UNSPECIFIED, |
|
L"Uncaught C++ exception", |
|
nullptr |
|
}; |
|
|
|
|
|
WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err) { |
|
return err != nullptr ? err->code : WINPTY_ERROR_SUCCESS; |
|
} |
|
|
|
|
|
|
|
WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err) { |
|
if (err != nullptr) { |
|
if (err->msgStatic != nullptr) { |
|
return err->msgStatic; |
|
} else { |
|
ASSERT(err->msgDynamic != nullptr); |
|
std::wstring *msgPtr = err->msgDynamic->get(); |
|
ASSERT(msgPtr != nullptr); |
|
return msgPtr->c_str(); |
|
} |
|
} else { |
|
return L"Success"; |
|
} |
|
} |
|
|
|
|
|
|
|
WINPTY_API void winpty_error_free(winpty_error_ptr_t err) { |
|
if (err != nullptr && err->msgDynamic != nullptr) { |
|
delete err->msgDynamic; |
|
delete err; |
|
} |
|
} |
|
|
|
static void translateException(winpty_error_ptr_t *&err) { |
|
winpty_error_ptr_t ret = nullptr; |
|
try { |
|
try { |
|
throw; |
|
} catch (const ReadBuffer::DecodeError&) { |
|
ret = const_cast<winpty_error_ptr_t>(&kBadRpcPacket); |
|
} catch (const LibWinptyException &e) { |
|
std::unique_ptr<winpty_error_t> obj(new winpty_error_t); |
|
obj->code = e.code(); |
|
obj->msgStatic = nullptr; |
|
obj->msgDynamic = |
|
new std::shared_ptr<std::wstring>(e.whatSharedStr()); |
|
ret = obj.release(); |
|
} catch (const WinptyException &e) { |
|
std::unique_ptr<winpty_error_t> obj(new winpty_error_t); |
|
std::shared_ptr<std::wstring> msg(new std::wstring(e.what())); |
|
obj->code = WINPTY_ERROR_UNSPECIFIED; |
|
obj->msgStatic = nullptr; |
|
obj->msgDynamic = new std::shared_ptr<std::wstring>(msg); |
|
ret = obj.release(); |
|
} |
|
} catch (const std::bad_alloc&) { |
|
ret = const_cast<winpty_error_ptr_t>(&kOutOfMemory); |
|
} catch (...) { |
|
ret = const_cast<winpty_error_ptr_t>(&kUncaughtException); |
|
} |
|
trace("libwinpty error: code=%u msg='%s'", |
|
static_cast<unsigned>(ret->code), |
|
utf8FromWide(winpty_error_msg(ret)).c_str()); |
|
if (err != nullptr) { |
|
*err = ret; |
|
} else { |
|
winpty_error_free(ret); |
|
} |
|
} |
|
|
|
#define API_TRY \ |
|
if (err != nullptr) { *err = nullptr; } \ |
|
try |
|
|
|
#define API_CATCH(ret) \ |
|
catch (...) { translateException(err); return (ret); } |
|
|
|
|
|
|
|
|
|
|
|
|
|
WINPTY_API winpty_config_t * |
|
winpty_config_new(UINT64 flags, winpty_error_ptr_t *err ) { |
|
API_TRY { |
|
ASSERT((flags & WINPTY_FLAG_MASK) == flags); |
|
std::unique_ptr<winpty_config_t> ret(new winpty_config_t); |
|
ret->flags = flags; |
|
return ret.release(); |
|
} API_CATCH(nullptr) |
|
} |
|
|
|
WINPTY_API void winpty_config_free(winpty_config_t *cfg) { |
|
delete cfg; |
|
} |
|
|
|
WINPTY_API void |
|
winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows) { |
|
ASSERT(cfg != nullptr && cols > 0 && rows > 0); |
|
cfg->cols = cols; |
|
cfg->rows = rows; |
|
} |
|
|
|
WINPTY_API void |
|
winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode) { |
|
ASSERT(cfg != nullptr && |
|
mouseMode >= WINPTY_MOUSE_MODE_NONE && |
|
mouseMode <= WINPTY_MOUSE_MODE_FORCE); |
|
cfg->mouseMode = mouseMode; |
|
} |
|
|
|
WINPTY_API void |
|
winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs) { |
|
ASSERT(cfg != nullptr && timeoutMs > 0); |
|
cfg->timeoutMs = timeoutMs; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace { |
|
|
|
|
|
|
|
|
|
|
|
class PendingIo { |
|
HANDLE m_file; |
|
OVERLAPPED &m_over; |
|
bool m_finished; |
|
public: |
|
|
|
|
|
PendingIo(HANDLE file, OVERLAPPED &over) : |
|
m_file(file), m_over(over), m_finished(false) {} |
|
~PendingIo() { |
|
if (!m_finished) { |
|
|
|
|
|
CancelIo(m_file); |
|
waitForCompletion(); |
|
} |
|
} |
|
std::tuple<BOOL, DWORD> waitForCompletion(DWORD &actual) WINPTY_NOEXCEPT { |
|
m_finished = true; |
|
const BOOL success = |
|
GetOverlappedResult(m_file, &m_over, &actual, TRUE); |
|
return std::make_tuple(success, GetLastError()); |
|
} |
|
std::tuple<BOOL, DWORD> waitForCompletion() WINPTY_NOEXCEPT { |
|
DWORD actual = 0; |
|
return waitForCompletion(actual); |
|
} |
|
}; |
|
|
|
} |
|
|
|
static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success, |
|
DWORD &lastError, DWORD &actual) { |
|
if (!success && lastError == ERROR_IO_PENDING) { |
|
PendingIo io(wp.controlPipe.get(), over); |
|
const HANDLE waitHandles[2] = { wp.ioEvent.get(), |
|
wp.agentProcess.get() }; |
|
DWORD waitRet = WaitForMultipleObjects( |
|
2, waitHandles, FALSE, wp.agentTimeoutMs); |
|
if (waitRet != WAIT_OBJECT_0) { |
|
|
|
|
|
if (waitRet == WAIT_OBJECT_0 + 1) { |
|
throw LibWinptyException(WINPTY_ERROR_AGENT_DIED, L"agent died"); |
|
} else if (waitRet == WAIT_TIMEOUT) { |
|
throw LibWinptyException(WINPTY_ERROR_AGENT_TIMEOUT, |
|
L"agent timed out"); |
|
} else if (waitRet == WAIT_FAILED) { |
|
throwWindowsError(L"WaitForMultipleObjects failed"); |
|
} else { |
|
ASSERT(false && |
|
"unexpected WaitForMultipleObjects return value"); |
|
} |
|
} |
|
std::tie(success, lastError) = io.waitForCompletion(actual); |
|
} |
|
} |
|
|
|
static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success, |
|
DWORD &lastError) { |
|
DWORD actual = 0; |
|
handlePendingIo(wp, over, success, lastError, actual); |
|
} |
|
|
|
static void handleReadWriteErrors(winpty_t &wp, BOOL success, DWORD lastError, |
|
const wchar_t *genericErrMsg) { |
|
if (!success) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
if (lastError == ERROR_BROKEN_PIPE || lastError == ERROR_NO_DATA || |
|
lastError == ERROR_PIPE_NOT_CONNECTED) { |
|
throw LibWinptyException(WINPTY_ERROR_LOST_CONNECTION, |
|
L"lost connection to agent"); |
|
} else { |
|
throwWindowsError(genericErrMsg, lastError); |
|
} |
|
} |
|
} |
|
|
|
|
|
static void |
|
connectControlPipe(winpty_t &wp) { |
|
OVERLAPPED over = {}; |
|
over.hEvent = wp.ioEvent.get(); |
|
BOOL success = ConnectNamedPipe(wp.controlPipe.get(), &over); |
|
DWORD lastError = GetLastError(); |
|
handlePendingIo(wp, over, success, lastError); |
|
if (!success && lastError == ERROR_PIPE_CONNECTED) { |
|
success = TRUE; |
|
} |
|
if (!success) { |
|
throwWindowsError(L"ConnectNamedPipe failed", lastError); |
|
} |
|
} |
|
|
|
static void writeData(winpty_t &wp, const void *data, size_t amount) { |
|
|
|
DWORD actual = 0; |
|
OVERLAPPED over = {}; |
|
over.hEvent = wp.ioEvent.get(); |
|
BOOL success = WriteFile(wp.controlPipe.get(), data, amount, |
|
&actual, &over); |
|
DWORD lastError = GetLastError(); |
|
if (!success) { |
|
handlePendingIo(wp, over, success, lastError, actual); |
|
handleReadWriteErrors(wp, success, lastError, L"WriteFile failed"); |
|
ASSERT(success); |
|
} |
|
|
|
ASSERT(actual == amount && "WriteFile wrote fewer bytes than requested"); |
|
} |
|
|
|
static inline WriteBuffer newPacket() { |
|
WriteBuffer packet; |
|
packet.putRawValue<uint64_t>(0); |
|
return packet; |
|
} |
|
|
|
static void writePacket(winpty_t &wp, WriteBuffer &packet) { |
|
const auto &buf = packet.buf(); |
|
packet.replaceRawValue<uint64_t>(0, buf.size()); |
|
writeData(wp, buf.data(), buf.size()); |
|
} |
|
|
|
static size_t readData(winpty_t &wp, void *data, size_t amount) { |
|
DWORD actual = 0; |
|
OVERLAPPED over = {}; |
|
over.hEvent = wp.ioEvent.get(); |
|
BOOL success = ReadFile(wp.controlPipe.get(), data, amount, |
|
&actual, &over); |
|
DWORD lastError = GetLastError(); |
|
if (!success) { |
|
handlePendingIo(wp, over, success, lastError, actual); |
|
handleReadWriteErrors(wp, success, lastError, L"ReadFile failed"); |
|
} |
|
return actual; |
|
} |
|
|
|
static void readAll(winpty_t &wp, void *data, size_t amount) { |
|
while (amount > 0) { |
|
const size_t chunk = readData(wp, data, amount); |
|
ASSERT(chunk <= amount && "readData result is larger than amount"); |
|
data = reinterpret_cast<char*>(data) + chunk; |
|
amount -= chunk; |
|
} |
|
} |
|
|
|
static uint64_t readUInt64(winpty_t &wp) { |
|
uint64_t ret = 0; |
|
readAll(wp, &ret, sizeof(ret)); |
|
return ret; |
|
} |
|
|
|
|
|
static ReadBuffer readPacket(winpty_t &wp) { |
|
const uint64_t packetSize = readUInt64(wp); |
|
if (packetSize < sizeof(packetSize) || packetSize > SIZE_MAX) { |
|
throwWinptyException(L"Agent RPC error: invalid packet size"); |
|
} |
|
const size_t payloadSize = packetSize - sizeof(packetSize); |
|
std::vector<char> bytes(payloadSize); |
|
readAll(wp, bytes.data(), bytes.size()); |
|
return ReadBuffer(std::move(bytes)); |
|
} |
|
|
|
static OwnedHandle createControlPipe(const std::wstring &name) { |
|
const auto sd = createPipeSecurityDescriptorOwnerFullControl(); |
|
if (!sd) { |
|
throwWinptyException( |
|
L"could not create the control pipe's SECURITY_DESCRIPTOR"); |
|
} |
|
SECURITY_ATTRIBUTES sa = {}; |
|
sa.nLength = sizeof(sa); |
|
sa.lpSecurityDescriptor = sd.get(); |
|
HANDLE ret = CreateNamedPipeW(name.c_str(), |
|
|
|
PIPE_ACCESS_DUPLEX | |
|
FILE_FLAG_FIRST_PIPE_INSTANCE | |
|
FILE_FLAG_OVERLAPPED, |
|
rejectRemoteClientsPipeFlag(), |
|
1, |
|
8192, |
|
256, |
|
30000, |
|
&sa); |
|
if (ret == INVALID_HANDLE_VALUE) { |
|
throwWindowsError(L"CreateNamedPipeW failed"); |
|
} |
|
return OwnedHandle(ret); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static OwnedHandle createEvent() { |
|
|
|
HANDLE h = CreateEventW(nullptr, TRUE, FALSE, nullptr); |
|
if (h == nullptr) { |
|
throwWindowsError(L"CreateEventW failed"); |
|
} |
|
return OwnedHandle(h); |
|
} |
|
|
|
|
|
|
|
static bool shouldShowConsoleWindow() { |
|
char buf[32]; |
|
return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0; |
|
} |
|
|
|
static bool shouldCreateBackgroundDesktop(bool &createUsingAgent) { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bool ret = !shouldShowConsoleWindow() && !isAtLeastWindows7(); |
|
const bool force = hasDebugFlag("force_desktop"); |
|
const bool force_spawn = hasDebugFlag("force_desktop_spawn"); |
|
const bool force_curproc = hasDebugFlag("force_desktop_curproc"); |
|
const bool suppress = hasDebugFlag("no_desktop"); |
|
if (force + force_spawn + force_curproc + suppress > 1) { |
|
trace("error: Only one of force_desktop, force_desktop_spawn, " |
|
"force_desktop_curproc, and no_desktop may be set"); |
|
} else if (force) { |
|
ret = true; |
|
} else if (force_spawn) { |
|
ret = true; |
|
createUsingAgent = true; |
|
} else if (force_curproc) { |
|
ret = true; |
|
createUsingAgent = false; |
|
} else if (suppress) { |
|
ret = false; |
|
} |
|
return ret; |
|
} |
|
|
|
static bool shouldSpecifyHideFlag() { |
|
const bool force = hasDebugFlag("force_sw_hide"); |
|
const bool suppress = hasDebugFlag("no_sw_hide"); |
|
bool ret = !shouldShowConsoleWindow(); |
|
if (force && suppress) { |
|
trace("error: Both the force_sw_hide and no_sw_hide flags are set"); |
|
} else if (force) { |
|
ret = true; |
|
} else if (suppress) { |
|
ret = false; |
|
} |
|
return ret; |
|
} |
|
|
|
static OwnedHandle startAgentProcess( |
|
const std::wstring &desktop, |
|
const std::wstring &controlPipeName, |
|
const std::wstring ¶ms, |
|
DWORD creationFlags, |
|
DWORD &agentPid) { |
|
const std::wstring exePath = findAgentProgram(); |
|
const std::wstring cmdline = |
|
(WStringBuilder(256) |
|
<< L"\"" << exePath << L"\" " |
|
<< controlPipeName << L' ' |
|
<< params).str_moved(); |
|
|
|
auto cmdlineV = vectorWithNulFromString(cmdline); |
|
auto desktopV = vectorWithNulFromString(desktop); |
|
|
|
|
|
STARTUPINFOW sui = {}; |
|
sui.cb = sizeof(sui); |
|
sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data(); |
|
|
|
if (shouldSpecifyHideFlag()) { |
|
sui.dwFlags |= STARTF_USESHOWWINDOW; |
|
sui.wShowWindow = SW_HIDE; |
|
} |
|
PROCESS_INFORMATION pi = {}; |
|
const BOOL success = |
|
CreateProcessW(exePath.c_str(), |
|
cmdlineV.data(), |
|
nullptr, nullptr, |
|
FALSE, |
|
creationFlags, |
|
nullptr, nullptr, |
|
&sui, &pi); |
|
if (!success) { |
|
const DWORD lastError = GetLastError(); |
|
const auto errStr = |
|
(WStringBuilder(256) |
|
<< L"winpty-agent CreateProcess failed: cmdline='" << cmdline |
|
<< L"' err=0x" << whexOfInt(lastError)).str_moved(); |
|
throw LibWinptyException( |
|
WINPTY_ERROR_AGENT_CREATION_FAILED, errStr.c_str()); |
|
} |
|
CloseHandle(pi.hThread); |
|
TRACE("Created agent successfully, pid=%u, cmdline=%s", |
|
static_cast<unsigned int>(pi.dwProcessId), |
|
utf8FromWide(cmdline).c_str()); |
|
agentPid = pi.dwProcessId; |
|
return OwnedHandle(pi.hProcess); |
|
} |
|
|
|
static void verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid) { |
|
const auto client = getNamedPipeClientProcessId(serverPipe); |
|
const auto success = std::get<0>(client); |
|
const auto lastError = std::get<2>(client); |
|
if (success == GetNamedPipeClientProcessId_Result::Success) { |
|
const auto clientPid = std::get<1>(client); |
|
if (clientPid != agentPid) { |
|
WStringBuilder errMsg; |
|
errMsg << L"Security check failed: pipe client pid (" << clientPid |
|
<< L") does not match agent pid (" << agentPid << L")"; |
|
throwWinptyException(errMsg.c_str()); |
|
} |
|
} else if (success == GetNamedPipeClientProcessId_Result::UnsupportedOs) { |
|
trace("Pipe client PID security check skipped: " |
|
"GetNamedPipeClientProcessId unsupported on this OS version"); |
|
} else { |
|
throwWindowsError(L"GetNamedPipeClientProcessId failed", lastError); |
|
} |
|
} |
|
|
|
static std::unique_ptr<winpty_t> |
|
createAgentSession(const winpty_config_t *cfg, |
|
const std::wstring &desktop, |
|
const std::wstring ¶ms, |
|
DWORD creationFlags) { |
|
std::unique_ptr<winpty_t> wp(new winpty_t); |
|
wp->agentTimeoutMs = cfg->timeoutMs; |
|
wp->ioEvent = createEvent(); |
|
|
|
|
|
const auto pipeName = |
|
L"\\\\.\\pipe\\winpty-control-" + GenRandom().uniqueName(); |
|
wp->controlPipe = createControlPipe(pipeName); |
|
|
|
DWORD agentPid = 0; |
|
wp->agentProcess = startAgentProcess( |
|
desktop, pipeName, params, creationFlags, agentPid); |
|
connectControlPipe(*wp.get()); |
|
verifyPipeClientPid(wp->controlPipe.get(), agentPid); |
|
|
|
return std::move(wp); |
|
} |
|
|
|
namespace { |
|
|
|
class AgentDesktop { |
|
public: |
|
virtual std::wstring name() = 0; |
|
virtual ~AgentDesktop() {} |
|
}; |
|
|
|
class AgentDesktopDirect : public AgentDesktop { |
|
public: |
|
AgentDesktopDirect(BackgroundDesktop &&desktop) : |
|
m_desktop(std::move(desktop)) |
|
{ |
|
} |
|
std::wstring name() override { return m_desktop.desktopName(); } |
|
private: |
|
BackgroundDesktop m_desktop; |
|
}; |
|
|
|
class AgentDesktopIndirect : public AgentDesktop { |
|
public: |
|
AgentDesktopIndirect(std::unique_ptr<winpty_t> &&wp, |
|
std::wstring &&desktopName) : |
|
m_wp(std::move(wp)), |
|
m_desktopName(std::move(desktopName)) |
|
{ |
|
} |
|
std::wstring name() override { return m_desktopName; } |
|
private: |
|
std::unique_ptr<winpty_t> m_wp; |
|
std::wstring m_desktopName; |
|
}; |
|
|
|
} |
|
|
|
std::unique_ptr<AgentDesktop> |
|
setupBackgroundDesktop(const winpty_config_t *cfg) { |
|
bool useDesktopAgent = |
|
!(cfg->flags & WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION); |
|
const bool useDesktop = shouldCreateBackgroundDesktop(useDesktopAgent); |
|
|
|
if (!useDesktop) { |
|
return std::unique_ptr<AgentDesktop>(); |
|
} |
|
|
|
if (useDesktopAgent) { |
|
auto wp = createAgentSession( |
|
cfg, std::wstring(), L"--create-desktop", DETACHED_PROCESS); |
|
|
|
|
|
auto packet = readPacket(*wp.get()); |
|
auto desktopName = packet.getWString(); |
|
packet.assertEof(); |
|
|
|
if (desktopName.empty()) { |
|
return std::unique_ptr<AgentDesktop>(); |
|
} else { |
|
return std::unique_ptr<AgentDesktop>( |
|
new AgentDesktopIndirect(std::move(wp), |
|
std::move(desktopName))); |
|
} |
|
} else { |
|
try { |
|
BackgroundDesktop desktop; |
|
return std::unique_ptr<AgentDesktop>(new AgentDesktopDirect( |
|
std::move(desktop))); |
|
} catch (const WinptyException &e) { |
|
trace("Error: failed to create background desktop, " |
|
"using original desktop instead: %s", |
|
utf8FromWide(e.what()).c_str()); |
|
return std::unique_ptr<AgentDesktop>(); |
|
} |
|
} |
|
} |
|
|
|
WINPTY_API winpty_t * |
|
winpty_open(const winpty_config_t *cfg, |
|
winpty_error_ptr_t *err ) { |
|
API_TRY { |
|
ASSERT(cfg != nullptr); |
|
dumpWindowsVersion(); |
|
dumpVersionToTrace(); |
|
|
|
|
|
auto desktop = setupBackgroundDesktop(cfg); |
|
const auto desktopName = desktop ? desktop->name() : std::wstring(); |
|
|
|
|
|
const auto params = |
|
(WStringBuilder(128) |
|
<< cfg->flags << L' ' |
|
<< cfg->mouseMode << L' ' |
|
<< cfg->cols << L' ' |
|
<< cfg->rows).str_moved(); |
|
auto wp = createAgentSession(cfg, desktopName, params, |
|
CREATE_NEW_CONSOLE); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
desktop.reset(); |
|
|
|
|
|
|
|
|
|
if (!desktopName.empty()) { |
|
wp->spawnDesktopName = getCurrentDesktopName(); |
|
} |
|
|
|
|
|
auto packet = readPacket(*wp.get()); |
|
wp->coninPipeName = packet.getWString(); |
|
wp->conoutPipeName = packet.getWString(); |
|
if (cfg->flags & WINPTY_FLAG_CONERR) { |
|
wp->conerrPipeName = packet.getWString(); |
|
} |
|
packet.assertEof(); |
|
|
|
return wp.release(); |
|
} API_CATCH(nullptr) |
|
} |
|
|
|
WINPTY_API HANDLE winpty_agent_process(winpty_t *wp) { |
|
ASSERT(wp != nullptr); |
|
return wp->agentProcess.get(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
static const wchar_t *cstrFromWStringOrNull(const std::wstring &str) { |
|
try { |
|
return str.c_str(); |
|
} catch (const std::bad_alloc&) { |
|
return nullptr; |
|
} |
|
} |
|
|
|
WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp) { |
|
ASSERT(wp != nullptr); |
|
return cstrFromWStringOrNull(wp->coninPipeName); |
|
} |
|
|
|
WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp) { |
|
ASSERT(wp != nullptr); |
|
return cstrFromWStringOrNull(wp->conoutPipeName); |
|
} |
|
|
|
WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp) { |
|
ASSERT(wp != nullptr); |
|
if (wp->conerrPipeName.empty()) { |
|
return nullptr; |
|
} else { |
|
return cstrFromWStringOrNull(wp->conerrPipeName); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace { |
|
|
|
|
|
|
|
class RpcOperation { |
|
public: |
|
RpcOperation(winpty_t &wp) : m_wp(wp) { |
|
if (m_wp.controlPipe.get() == nullptr) { |
|
throwWinptyException(L"Agent shutdown due to RPC failure"); |
|
} |
|
} |
|
~RpcOperation() { |
|
if (!m_success) { |
|
trace("~RpcOperation: Closing control pipe"); |
|
m_wp.controlPipe.dispose(true); |
|
} |
|
} |
|
void success() { m_success = true; } |
|
private: |
|
winpty_t &m_wp; |
|
bool m_success = false; |
|
}; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
static std::wstring wstringFromEnvBlock(const wchar_t *env) { |
|
std::wstring envStr; |
|
if (env != NULL) { |
|
const wchar_t *p = env; |
|
while (*p != L'\0') { |
|
p += wcslen(p) + 1; |
|
} |
|
p++; |
|
envStr.assign(env, p); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const auto envStrSz = envStr.size(); |
|
if (envStrSz == 1) { |
|
ASSERT(envStr[0] == L'\0'); |
|
envStr.push_back(L'\0'); |
|
} else { |
|
ASSERT(envStrSz >= 3); |
|
ASSERT(envStr[envStrSz - 3] != L'\0'); |
|
ASSERT(envStr[envStrSz - 2] == L'\0'); |
|
ASSERT(envStr[envStrSz - 1] == L'\0'); |
|
} |
|
} |
|
return envStr; |
|
} |
|
|
|
WINPTY_API winpty_spawn_config_t * |
|
winpty_spawn_config_new(UINT64 winptyFlags, |
|
LPCWSTR appname , |
|
LPCWSTR cmdline , |
|
LPCWSTR cwd , |
|
LPCWSTR env , |
|
winpty_error_ptr_t *err ) { |
|
API_TRY { |
|
ASSERT((winptyFlags & WINPTY_SPAWN_FLAG_MASK) == winptyFlags); |
|
std::unique_ptr<winpty_spawn_config_t> cfg(new winpty_spawn_config_t); |
|
cfg->winptyFlags = winptyFlags; |
|
if (appname != nullptr) { cfg->appname = appname; } |
|
if (cmdline != nullptr) { cfg->cmdline = cmdline; } |
|
if (cwd != nullptr) { cfg->cwd = cwd; } |
|
if (env != nullptr) { cfg->env = wstringFromEnvBlock(env); } |
|
return cfg.release(); |
|
} API_CATCH(nullptr) |
|
} |
|
|
|
WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg) { |
|
delete cfg; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
static inline HANDLE handleFromInt64(int64_t i) { |
|
return reinterpret_cast<HANDLE>(static_cast<intptr_t>(i)); |
|
} |
|
|
|
|
|
|
|
static inline OwnedHandle stealHandle(HANDLE process, HANDLE handle) { |
|
HANDLE result = nullptr; |
|
if (!DuplicateHandle(process, handle, |
|
GetCurrentProcess(), |
|
&result, 0, FALSE, |
|
DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) { |
|
throwWindowsError(L"DuplicateHandle of process handle"); |
|
} |
|
return OwnedHandle(result); |
|
} |
|
|
|
WINPTY_API BOOL |
|
winpty_spawn(winpty_t *wp, |
|
const winpty_spawn_config_t *cfg, |
|
HANDLE *process_handle , |
|
HANDLE *thread_handle , |
|
DWORD *create_process_error , |
|
winpty_error_ptr_t *err ) { |
|
API_TRY { |
|
ASSERT(wp != nullptr && cfg != nullptr); |
|
|
|
if (process_handle != nullptr) { *process_handle = nullptr; } |
|
if (thread_handle != nullptr) { *thread_handle = nullptr; } |
|
if (create_process_error != nullptr) { *create_process_error = 0; } |
|
|
|
LockGuard<Mutex> lock(wp->mutex); |
|
RpcOperation rpc(*wp); |
|
|
|
|
|
auto packet = newPacket(); |
|
packet.putInt32(AgentMsg::StartProcess); |
|
packet.putInt64(cfg->winptyFlags); |
|
packet.putInt32(process_handle != nullptr); |
|
packet.putInt32(thread_handle != nullptr); |
|
packet.putWString(cfg->appname); |
|
packet.putWString(cfg->cmdline); |
|
packet.putWString(cfg->cwd); |
|
packet.putWString(cfg->env); |
|
packet.putWString(wp->spawnDesktopName); |
|
writePacket(*wp, packet); |
|
|
|
|
|
auto reply = readPacket(*wp); |
|
const auto result = static_cast<StartProcessResult>(reply.getInt32()); |
|
if (result == StartProcessResult::CreateProcessFailed) { |
|
const DWORD lastError = reply.getInt32(); |
|
reply.assertEof(); |
|
if (create_process_error != nullptr) { |
|
*create_process_error = lastError; |
|
} |
|
rpc.success(); |
|
throw LibWinptyException(WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED, |
|
L"CreateProcess failed"); |
|
} else if (result == StartProcessResult::ProcessCreated) { |
|
const HANDLE remoteProcess = handleFromInt64(reply.getInt64()); |
|
const HANDLE remoteThread = handleFromInt64(reply.getInt64()); |
|
reply.assertEof(); |
|
OwnedHandle localProcess; |
|
OwnedHandle localThread; |
|
if (remoteProcess != nullptr) { |
|
localProcess = |
|
stealHandle(wp->agentProcess.get(), remoteProcess); |
|
} |
|
if (remoteThread != nullptr) { |
|
localThread = |
|
stealHandle(wp->agentProcess.get(), remoteThread); |
|
} |
|
if (process_handle != nullptr) { |
|
*process_handle = localProcess.release(); |
|
} |
|
if (thread_handle != nullptr) { |
|
*thread_handle = localThread.release(); |
|
} |
|
rpc.success(); |
|
} else { |
|
throwWinptyException( |
|
L"Agent RPC error: invalid StartProcessResult"); |
|
} |
|
return TRUE; |
|
} API_CATCH(FALSE) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
WINPTY_API BOOL |
|
winpty_set_size(winpty_t *wp, int cols, int rows, |
|
winpty_error_ptr_t *err ) { |
|
API_TRY { |
|
ASSERT(wp != nullptr && cols > 0 && rows > 0); |
|
LockGuard<Mutex> lock(wp->mutex); |
|
RpcOperation rpc(*wp); |
|
auto packet = newPacket(); |
|
packet.putInt32(AgentMsg::SetSize); |
|
packet.putInt32(cols); |
|
packet.putInt32(rows); |
|
writePacket(*wp, packet); |
|
readPacket(*wp).assertEof(); |
|
rpc.success(); |
|
return TRUE; |
|
} API_CATCH(FALSE) |
|
} |
|
|
|
WINPTY_API int |
|
winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount, |
|
winpty_error_ptr_t *err ) { |
|
API_TRY { |
|
ASSERT(wp != nullptr); |
|
ASSERT(processList != nullptr); |
|
LockGuard<Mutex> lock(wp->mutex); |
|
RpcOperation rpc(*wp); |
|
auto packet = newPacket(); |
|
packet.putInt32(AgentMsg::GetConsoleProcessList); |
|
writePacket(*wp, packet); |
|
auto reply = readPacket(*wp); |
|
|
|
auto actualProcessCount = reply.getInt32(); |
|
|
|
if (actualProcessCount <= processCount) { |
|
for (auto i = 0; i < actualProcessCount; i++) { |
|
processList[i] = reply.getInt32(); |
|
} |
|
} |
|
|
|
reply.assertEof(); |
|
rpc.success(); |
|
return actualProcessCount; |
|
} API_CATCH(0) |
|
} |
|
|
|
WINPTY_API void winpty_free(winpty_t *wp) { |
|
|
|
|
|
|
|
delete wp; |
|
} |
|
|