// 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 #include #include #include #include #include #include #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(&kBadRpcPacket); } catch (const LibWinptyException &e) { std::unique_ptr obj(new winpty_error_t); obj->code = e.code(); obj->msgStatic = nullptr; obj->msgDynamic = new std::shared_ptr(e.whatSharedStr()); ret = obj.release(); } catch (const WinptyException &e) { std::unique_ptr obj(new winpty_error_t); std::shared_ptr msg(new std::wstring(e.what())); obj->code = WINPTY_ERROR_UNSPECIFIED; obj->msgStatic = nullptr; obj->msgDynamic = new std::shared_ptr(msg); ret = obj.release(); } } catch (const std::bad_alloc&) { ret = const_cast(&kOutOfMemory); } catch (...) { ret = const_cast(&kUncaughtException); } trace("libwinpty error: code=%u msg='%s'", static_cast(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 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 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 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(0); // Reserve space for size. return packet; } static void writePacket(winpty_t &wp, WriteBuffer &packet) { const auto &buf = packet.buf(); packet.replaceRawValue(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(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 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(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 createAgentSession(const winpty_config_t *cfg, const std::wstring &desktop, const std::wstring ¶ms, DWORD creationFlags) { std::unique_ptr 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 &&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 m_wp; std::wstring m_desktopName; }; } // anonymous namespace std::unique_ptr 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(); } 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(); } else { return std::unique_ptr( new AgentDesktopIndirect(std::move(wp), std::move(desktopName))); } } else { try { BackgroundDesktop desktop; return std::unique_ptr(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(); } } } 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 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(static_cast(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 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(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 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 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; }