+++ /dev/null
-// Copyright (c) 2011-2016 Ryan Prichard
-//
-// Permission is hereby granted, free of charge, to any person obtaining a copy
-// of this software and associated documentation files (the "Software"), to
-// deal in the Software without restriction, including without limitation the
-// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
-// sell copies of the Software, and to permit persons to whom the Software is
-// furnished to do so, subject to the following conditions:
-//
-// The above copyright notice and this permission notice shall be included in
-// all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
-// IN THE SOFTWARE.
-
-#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"
-
-
-
-/*****************************************************************************
- * Error handling -- translate C++ exceptions to an optional error object
- * output and log the result. */
-
-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
-};
-
-/* Gets the error code from the error object. */
-WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err) {
- return err != nullptr ? err->code : WINPTY_ERROR_SUCCESS;
-}
-
-/* Returns a textual representation of the error. The string is freed when
- * the error is freed. */
-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";
- }
-}
-
-/* Free the error object. Every error returned from the winpty API must be
- * freed. */
-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); }
-
-
-
-/*****************************************************************************
- * Configuration of a new agent. */
-
-WINPTY_API winpty_config_t *
-winpty_config_new(UINT64 flags, winpty_error_ptr_t *err /*OPTIONAL*/) {
- 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;
-}
-
-
-
-/*****************************************************************************
- * Agent I/O. */
-
-namespace {
-
-// Once an I/O operation fails with ERROR_IO_PENDING, the caller *must* wait
-// for it to complete, even after calling CancelIo on it! See
-// https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613. This
-// class enforces that requirement.
-class PendingIo {
- HANDLE m_file;
- OVERLAPPED &m_over;
- bool m_finished;
-public:
- // The file handle and OVERLAPPED object must live as long as the PendingIo
- // object.
- PendingIo(HANDLE file, OVERLAPPED &over) :
- m_file(file), m_over(over), m_finished(false) {}
- ~PendingIo() {
- if (!m_finished) {
- // We're not usually that interested in CancelIo's return value.
- // In any case, we must not throw an exception in this dtor.
- 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);
- }
-};
-
-} // anonymous namespace
-
-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) {
- // The I/O is still pending. Cancel it, close the I/O event, and
- // throw an exception.
- 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 the pipe connection is broken after it's been connected, then
- // later I/O operations fail with ERROR_BROKEN_PIPE (reads) or
- // ERROR_NO_DATA (writes). With Wine, they may also fail with
- // ERROR_PIPE_NOT_CONNECTED. See this gist[1].
- //
- // [1] https://gist.github.com/rprichard/8dd8ca134b39534b7da2733994aa07ba
- 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);
- }
- }
-}
-
-// Calls ConnectNamedPipe to wait until the agent connects to the control pipe.
-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) {
- // Perform a single pipe write.
- 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);
- }
- // TODO: Can a partial write actually happen somehow?
- ASSERT(actual == amount && "WriteFile wrote fewer bytes than requested");
-}
-
-static inline WriteBuffer newPacket() {
- WriteBuffer packet;
- packet.putRawValue<uint64_t>(0); // Reserve space for size.
- 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;
-}
-
-// Returns a reply packet's payload.
-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(),
- /*dwOpenMode=*/
- PIPE_ACCESS_DUPLEX |
- FILE_FLAG_FIRST_PIPE_INSTANCE |
- FILE_FLAG_OVERLAPPED,
- /*dwPipeMode=*/rejectRemoteClientsPipeFlag(),
- /*nMaxInstances=*/1,
- /*nOutBufferSize=*/8192,
- /*nInBufferSize=*/256,
- /*nDefaultTimeOut=*/30000,
- &sa);
- if (ret == INVALID_HANDLE_VALUE) {
- throwWindowsError(L"CreateNamedPipeW failed");
- }
- return OwnedHandle(ret);
-}
-
-
-
-/*****************************************************************************
- * Start the agent. */
-
-static OwnedHandle createEvent() {
- // manual reset, initially unset
- HANDLE h = CreateEventW(nullptr, TRUE, FALSE, nullptr);
- if (h == nullptr) {
- throwWindowsError(L"CreateEventW failed");
- }
- return OwnedHandle(h);
-}
-
-// For debugging purposes, provide a way to keep the console on the main window
-// station, visible.
-static bool shouldShowConsoleWindow() {
- char buf[32];
- return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0;
-}
-
-static bool shouldCreateBackgroundDesktop(bool &createUsingAgent) {
- // Prior to Windows 7, winpty's repeated selection-deselection loop
- // prevented the user from interacting with their *visible* console
- // windows, unless we placed the console onto a background desktop.
- // The SetProcessWindowStation call interferes with the clipboard and
- // isn't thread-safe, though[1]. The call should perhaps occur in a
- // special agent subprocess. Spawning a process in a background desktop
- // also breaks ConEmu, but marking the process SW_HIDE seems to correct
- // that[2].
- //
- // Windows 7 moved a lot of console handling out of csrss.exe and into
- // a per-console conhost.exe process, which may explain why it isn't
- // affected.
- //
- // This is a somewhat risky change, so there are low-level flags to
- // assist in debugging if there are issues.
- //
- // [1] https://github.com/rprichard/winpty/issues/58
- // [2] https://github.com/rprichard/winpty/issues/70
- 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);
-
- // Start the agent.
- 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,
- /*bInheritHandles=*/FALSE,
- /*dwCreationFlags=*/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();
-
- // Create control server pipe.
- 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;
-};
-
-} // anonymous namespace
-
-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);
-
- // Read the desktop name.
- 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 /*OPTIONAL*/) {
- API_TRY {
- ASSERT(cfg != nullptr);
- dumpWindowsVersion();
- dumpVersionToTrace();
-
- // Setup a background desktop for the agent.
- auto desktop = setupBackgroundDesktop(cfg);
- const auto desktopName = desktop ? desktop->name() : std::wstring();
-
- // Start the primary agent session.
- 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);
-
- // Close handles to the background desktop and restore the original
- // window station. This must wait until we know the agent is running
- // -- if we close these handles too soon, then the desktop and
- // windowstation will be destroyed before the agent can connect with
- // them.
- //
- // If we used a separate agent process to create the desktop, we
- // disconnect from that process here, allowing it to exit.
- desktop.reset();
-
- // If we ran the agent process on a background desktop, then when we
- // spawn a child process from the agent, it will need to be explicitly
- // placed back onto the original desktop.
- if (!desktopName.empty()) {
- wp->spawnDesktopName = getCurrentDesktopName();
- }
-
- // Get the CONIN/CONOUT pipe names.
- 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();
-}
-
-
-
-/*****************************************************************************
- * I/O pipes. */
-
-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);
- }
-}
-
-
-
-/*****************************************************************************
- * winpty agent RPC calls. */
-
-namespace {
-
-// Close the control pipe if something goes wrong with the pipe communication,
-// which could leave the control pipe in an inconsistent state.
-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;
-};
-
-} // anonymous namespace
-
-
-
-/*****************************************************************************
- * winpty agent RPC call: process creation. */
-
-// Return a std::wstring containing every character of the environment block.
-// Typically, the block is non-empty, so the std::wstring returned ends with
-// two NUL terminators. (These two terminators are counted in size(), so
-// calling c_str() produces a triply-terminated string.)
-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);
-
- // Assuming the environment was non-empty, envStr now ends with two NUL
- // terminators.
- //
- // If the environment were empty, though, then envStr would only be
- // singly terminated, but the MSDN documentation thinks an env block is
- // always doubly-terminated, so add an extra NUL just in case it
- // matters.
- 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 /*OPTIONAL*/,
- LPCWSTR cmdline /*OPTIONAL*/,
- LPCWSTR cwd /*OPTIONAL*/,
- LPCWSTR env /*OPTIONAL*/,
- winpty_error_ptr_t *err /*OPTIONAL*/) {
- 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;
-}
-
-// It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it
-// back to 64-bits. See the MSDN article, "Interprocess Communication Between
-// 32-bit and 64-bit Applications".
-// https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx
-static inline HANDLE handleFromInt64(int64_t i) {
- return reinterpret_cast<HANDLE>(static_cast<intptr_t>(i));
-}
-
-// Given a process and a handle in that process, duplicate the handle into the
-// current process and close it in the originating process.
-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 /*OPTIONAL*/,
- HANDLE *thread_handle /*OPTIONAL*/,
- DWORD *create_process_error /*OPTIONAL*/,
- winpty_error_ptr_t *err /*OPTIONAL*/) {
- 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);
-
- // Send spawn request.
- 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);
-
- // Receive reply.
- 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 agent RPC calls: everything else */
-
-WINPTY_API BOOL
-winpty_set_size(winpty_t *wp, int cols, int rows,
- winpty_error_ptr_t *err /*OPTIONAL*/) {
- 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 /*OPTIONAL*/) {
- 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) {
- // At least in principle, CloseHandle can fail, so this deletion can
- // fail. It won't throw an exception, but maybe there's an error that
- // should be propagated?
- delete wp;
-}