1 // Copyright (c) 2011-2016 Ryan Prichard
3 // Permission is hereby granted, free of charge, to any person obtaining a copy
4 // of this software and associated documentation files (the "Software"), to
5 // deal in the Software without restriction, including without limitation the
6 // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 // sell copies of the Software, and to permit persons to whom the Software is
8 // furnished to do so, subject to the following conditions:
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
31 #include "../include/winpty.h"
33 #include "../shared/AgentMsg.h"
34 #include "../shared/BackgroundDesktop.h"
35 #include "../shared/Buffer.h"
36 #include "../shared/DebugClient.h"
37 #include "../shared/GenRandom.h"
38 #include "../shared/OwnedHandle.h"
39 #include "../shared/StringBuilder.h"
40 #include "../shared/StringUtil.h"
41 #include "../shared/WindowsSecurity.h"
42 #include "../shared/WindowsVersion.h"
43 #include "../shared/WinptyAssert.h"
44 #include "../shared/WinptyException.h"
45 #include "../shared/WinptyVersion.h"
47 #include "AgentLocation.h"
48 #include "LibWinptyException.h"
49 #include "WinptyInternal.h"
53 /*****************************************************************************
54 * Error handling -- translate C++ exceptions to an optional error object
55 * output and log the result. */
57 static const winpty_error_s kOutOfMemory = {
58 WINPTY_ERROR_OUT_OF_MEMORY,
63 static const winpty_error_s kBadRpcPacket = {
64 WINPTY_ERROR_UNSPECIFIED,
69 static const winpty_error_s kUncaughtException = {
70 WINPTY_ERROR_UNSPECIFIED,
71 L"Uncaught C++ exception",
75 /* Gets the error code from the error object. */
76 WINPTY_API winpty_result_t winpty_error_code(winpty_error_ptr_t err) {
77 return err != nullptr ? err->code : WINPTY_ERROR_SUCCESS;
80 /* Returns a textual representation of the error. The string is freed when
81 * the error is freed. */
82 WINPTY_API LPCWSTR winpty_error_msg(winpty_error_ptr_t err) {
84 if (err->msgStatic != nullptr) {
85 return err->msgStatic;
87 ASSERT(err->msgDynamic != nullptr);
88 std::wstring *msgPtr = err->msgDynamic->get();
89 ASSERT(msgPtr != nullptr);
90 return msgPtr->c_str();
97 /* Free the error object. Every error returned from the winpty API must be
99 WINPTY_API void winpty_error_free(winpty_error_ptr_t err) {
100 if (err != nullptr && err->msgDynamic != nullptr) {
101 delete err->msgDynamic;
106 static void translateException(winpty_error_ptr_t *&err) {
107 winpty_error_ptr_t ret = nullptr;
111 } catch (const ReadBuffer::DecodeError&) {
112 ret = const_cast<winpty_error_ptr_t>(&kBadRpcPacket);
113 } catch (const LibWinptyException &e) {
114 std::unique_ptr<winpty_error_t> obj(new winpty_error_t);
115 obj->code = e.code();
116 obj->msgStatic = nullptr;
118 new std::shared_ptr<std::wstring>(e.whatSharedStr());
120 } catch (const WinptyException &e) {
121 std::unique_ptr<winpty_error_t> obj(new winpty_error_t);
122 std::shared_ptr<std::wstring> msg(new std::wstring(e.what()));
123 obj->code = WINPTY_ERROR_UNSPECIFIED;
124 obj->msgStatic = nullptr;
125 obj->msgDynamic = new std::shared_ptr<std::wstring>(msg);
128 } catch (const std::bad_alloc&) {
129 ret = const_cast<winpty_error_ptr_t>(&kOutOfMemory);
131 ret = const_cast<winpty_error_ptr_t>(&kUncaughtException);
133 trace("libwinpty error: code=%u msg='%s'",
134 static_cast<unsigned>(ret->code),
135 utf8FromWide(winpty_error_msg(ret)).c_str());
136 if (err != nullptr) {
139 winpty_error_free(ret);
144 if (err != nullptr) { *err = nullptr; } \
147 #define API_CATCH(ret) \
148 catch (...) { translateException(err); return (ret); }
152 /*****************************************************************************
153 * Configuration of a new agent. */
155 WINPTY_API winpty_config_t *
156 winpty_config_new(UINT64 flags, winpty_error_ptr_t *err /*OPTIONAL*/) {
158 ASSERT((flags & WINPTY_FLAG_MASK) == flags);
159 std::unique_ptr<winpty_config_t> ret(new winpty_config_t);
161 return ret.release();
165 WINPTY_API void winpty_config_free(winpty_config_t *cfg) {
170 winpty_config_set_initial_size(winpty_config_t *cfg, int cols, int rows) {
171 ASSERT(cfg != nullptr && cols > 0 && rows > 0);
177 winpty_config_set_mouse_mode(winpty_config_t *cfg, int mouseMode) {
178 ASSERT(cfg != nullptr &&
179 mouseMode >= WINPTY_MOUSE_MODE_NONE &&
180 mouseMode <= WINPTY_MOUSE_MODE_FORCE);
181 cfg->mouseMode = mouseMode;
185 winpty_config_set_agent_timeout(winpty_config_t *cfg, DWORD timeoutMs) {
186 ASSERT(cfg != nullptr && timeoutMs > 0);
187 cfg->timeoutMs = timeoutMs;
192 /*****************************************************************************
197 // Once an I/O operation fails with ERROR_IO_PENDING, the caller *must* wait
198 // for it to complete, even after calling CancelIo on it! See
199 // https://blogs.msdn.microsoft.com/oldnewthing/20110202-00/?p=11613. This
200 // class enforces that requirement.
206 // The file handle and OVERLAPPED object must live as long as the PendingIo
208 PendingIo(HANDLE file, OVERLAPPED &over) :
209 m_file(file), m_over(over), m_finished(false) {}
212 // We're not usually that interested in CancelIo's return value.
213 // In any case, we must not throw an exception in this dtor.
218 std::tuple<BOOL, DWORD> waitForCompletion(DWORD &actual) WINPTY_NOEXCEPT {
221 GetOverlappedResult(m_file, &m_over, &actual, TRUE);
222 return std::make_tuple(success, GetLastError());
224 std::tuple<BOOL, DWORD> waitForCompletion() WINPTY_NOEXCEPT {
226 return waitForCompletion(actual);
230 } // anonymous namespace
232 static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success,
233 DWORD &lastError, DWORD &actual) {
234 if (!success && lastError == ERROR_IO_PENDING) {
235 PendingIo io(wp.controlPipe.get(), over);
236 const HANDLE waitHandles[2] = { wp.ioEvent.get(),
237 wp.agentProcess.get() };
238 DWORD waitRet = WaitForMultipleObjects(
239 2, waitHandles, FALSE, wp.agentTimeoutMs);
240 if (waitRet != WAIT_OBJECT_0) {
241 // The I/O is still pending. Cancel it, close the I/O event, and
242 // throw an exception.
243 if (waitRet == WAIT_OBJECT_0 + 1) {
244 throw LibWinptyException(WINPTY_ERROR_AGENT_DIED, L"agent died");
245 } else if (waitRet == WAIT_TIMEOUT) {
246 throw LibWinptyException(WINPTY_ERROR_AGENT_TIMEOUT,
248 } else if (waitRet == WAIT_FAILED) {
249 throwWindowsError(L"WaitForMultipleObjects failed");
252 "unexpected WaitForMultipleObjects return value");
255 std::tie(success, lastError) = io.waitForCompletion(actual);
259 static void handlePendingIo(winpty_t &wp, OVERLAPPED &over, BOOL &success,
262 handlePendingIo(wp, over, success, lastError, actual);
265 static void handleReadWriteErrors(winpty_t &wp, BOOL success, DWORD lastError,
266 const wchar_t *genericErrMsg) {
268 // If the pipe connection is broken after it's been connected, then
269 // later I/O operations fail with ERROR_BROKEN_PIPE (reads) or
270 // ERROR_NO_DATA (writes). With Wine, they may also fail with
271 // ERROR_PIPE_NOT_CONNECTED. See this gist[1].
273 // [1] https://gist.github.com/rprichard/8dd8ca134b39534b7da2733994aa07ba
274 if (lastError == ERROR_BROKEN_PIPE || lastError == ERROR_NO_DATA ||
275 lastError == ERROR_PIPE_NOT_CONNECTED) {
276 throw LibWinptyException(WINPTY_ERROR_LOST_CONNECTION,
277 L"lost connection to agent");
279 throwWindowsError(genericErrMsg, lastError);
284 // Calls ConnectNamedPipe to wait until the agent connects to the control pipe.
286 connectControlPipe(winpty_t &wp) {
287 OVERLAPPED over = {};
288 over.hEvent = wp.ioEvent.get();
289 BOOL success = ConnectNamedPipe(wp.controlPipe.get(), &over);
290 DWORD lastError = GetLastError();
291 handlePendingIo(wp, over, success, lastError);
292 if (!success && lastError == ERROR_PIPE_CONNECTED) {
296 throwWindowsError(L"ConnectNamedPipe failed", lastError);
300 static void writeData(winpty_t &wp, const void *data, size_t amount) {
301 // Perform a single pipe write.
303 OVERLAPPED over = {};
304 over.hEvent = wp.ioEvent.get();
305 BOOL success = WriteFile(wp.controlPipe.get(), data, amount,
307 DWORD lastError = GetLastError();
309 handlePendingIo(wp, over, success, lastError, actual);
310 handleReadWriteErrors(wp, success, lastError, L"WriteFile failed");
313 // TODO: Can a partial write actually happen somehow?
314 ASSERT(actual == amount && "WriteFile wrote fewer bytes than requested");
317 static inline WriteBuffer newPacket() {
319 packet.putRawValue<uint64_t>(0); // Reserve space for size.
323 static void writePacket(winpty_t &wp, WriteBuffer &packet) {
324 const auto &buf = packet.buf();
325 packet.replaceRawValue<uint64_t>(0, buf.size());
326 writeData(wp, buf.data(), buf.size());
329 static size_t readData(winpty_t &wp, void *data, size_t amount) {
331 OVERLAPPED over = {};
332 over.hEvent = wp.ioEvent.get();
333 BOOL success = ReadFile(wp.controlPipe.get(), data, amount,
335 DWORD lastError = GetLastError();
337 handlePendingIo(wp, over, success, lastError, actual);
338 handleReadWriteErrors(wp, success, lastError, L"ReadFile failed");
343 static void readAll(winpty_t &wp, void *data, size_t amount) {
345 const size_t chunk = readData(wp, data, amount);
346 ASSERT(chunk <= amount && "readData result is larger than amount");
347 data = reinterpret_cast<char*>(data) + chunk;
352 static uint64_t readUInt64(winpty_t &wp) {
354 readAll(wp, &ret, sizeof(ret));
358 // Returns a reply packet's payload.
359 static ReadBuffer readPacket(winpty_t &wp) {
360 const uint64_t packetSize = readUInt64(wp);
361 if (packetSize < sizeof(packetSize) || packetSize > SIZE_MAX) {
362 throwWinptyException(L"Agent RPC error: invalid packet size");
364 const size_t payloadSize = packetSize - sizeof(packetSize);
365 std::vector<char> bytes(payloadSize);
366 readAll(wp, bytes.data(), bytes.size());
367 return ReadBuffer(std::move(bytes));
370 static OwnedHandle createControlPipe(const std::wstring &name) {
371 const auto sd = createPipeSecurityDescriptorOwnerFullControl();
373 throwWinptyException(
374 L"could not create the control pipe's SECURITY_DESCRIPTOR");
376 SECURITY_ATTRIBUTES sa = {};
377 sa.nLength = sizeof(sa);
378 sa.lpSecurityDescriptor = sd.get();
379 HANDLE ret = CreateNamedPipeW(name.c_str(),
382 FILE_FLAG_FIRST_PIPE_INSTANCE |
383 FILE_FLAG_OVERLAPPED,
384 /*dwPipeMode=*/rejectRemoteClientsPipeFlag(),
386 /*nOutBufferSize=*/8192,
387 /*nInBufferSize=*/256,
388 /*nDefaultTimeOut=*/30000,
390 if (ret == INVALID_HANDLE_VALUE) {
391 throwWindowsError(L"CreateNamedPipeW failed");
393 return OwnedHandle(ret);
398 /*****************************************************************************
399 * Start the agent. */
401 static OwnedHandle createEvent() {
402 // manual reset, initially unset
403 HANDLE h = CreateEventW(nullptr, TRUE, FALSE, nullptr);
405 throwWindowsError(L"CreateEventW failed");
407 return OwnedHandle(h);
410 // For debugging purposes, provide a way to keep the console on the main window
412 static bool shouldShowConsoleWindow() {
414 return GetEnvironmentVariableA("WINPTY_SHOW_CONSOLE", buf, sizeof(buf)) > 0;
417 static bool shouldCreateBackgroundDesktop(bool &createUsingAgent) {
418 // Prior to Windows 7, winpty's repeated selection-deselection loop
419 // prevented the user from interacting with their *visible* console
420 // windows, unless we placed the console onto a background desktop.
421 // The SetProcessWindowStation call interferes with the clipboard and
422 // isn't thread-safe, though[1]. The call should perhaps occur in a
423 // special agent subprocess. Spawning a process in a background desktop
424 // also breaks ConEmu, but marking the process SW_HIDE seems to correct
427 // Windows 7 moved a lot of console handling out of csrss.exe and into
428 // a per-console conhost.exe process, which may explain why it isn't
431 // This is a somewhat risky change, so there are low-level flags to
432 // assist in debugging if there are issues.
434 // [1] https://github.com/rprichard/winpty/issues/58
435 // [2] https://github.com/rprichard/winpty/issues/70
436 bool ret = !shouldShowConsoleWindow() && !isAtLeastWindows7();
437 const bool force = hasDebugFlag("force_desktop");
438 const bool force_spawn = hasDebugFlag("force_desktop_spawn");
439 const bool force_curproc = hasDebugFlag("force_desktop_curproc");
440 const bool suppress = hasDebugFlag("no_desktop");
441 if (force + force_spawn + force_curproc + suppress > 1) {
442 trace("error: Only one of force_desktop, force_desktop_spawn, "
443 "force_desktop_curproc, and no_desktop may be set");
446 } else if (force_spawn) {
448 createUsingAgent = true;
449 } else if (force_curproc) {
451 createUsingAgent = false;
452 } else if (suppress) {
458 static bool shouldSpecifyHideFlag() {
459 const bool force = hasDebugFlag("force_sw_hide");
460 const bool suppress = hasDebugFlag("no_sw_hide");
461 bool ret = !shouldShowConsoleWindow();
462 if (force && suppress) {
463 trace("error: Both the force_sw_hide and no_sw_hide flags are set");
466 } else if (suppress) {
472 static OwnedHandle startAgentProcess(
473 const std::wstring &desktop,
474 const std::wstring &controlPipeName,
475 const std::wstring ¶ms,
478 const std::wstring exePath = findAgentProgram();
479 const std::wstring cmdline =
481 << L"\"" << exePath << L"\" "
482 << controlPipeName << L' '
483 << params).str_moved();
485 auto cmdlineV = vectorWithNulFromString(cmdline);
486 auto desktopV = vectorWithNulFromString(desktop);
489 STARTUPINFOW sui = {};
490 sui.cb = sizeof(sui);
491 sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data();
493 if (shouldSpecifyHideFlag()) {
494 sui.dwFlags |= STARTF_USESHOWWINDOW;
495 sui.wShowWindow = SW_HIDE;
497 PROCESS_INFORMATION pi = {};
499 CreateProcessW(exePath.c_str(),
502 /*bInheritHandles=*/FALSE,
503 /*dwCreationFlags=*/creationFlags,
507 const DWORD lastError = GetLastError();
510 << L"winpty-agent CreateProcess failed: cmdline='" << cmdline
511 << L"' err=0x" << whexOfInt(lastError)).str_moved();
512 throw LibWinptyException(
513 WINPTY_ERROR_AGENT_CREATION_FAILED, errStr.c_str());
515 CloseHandle(pi.hThread);
516 TRACE("Created agent successfully, pid=%u, cmdline=%s",
517 static_cast<unsigned int>(pi.dwProcessId),
518 utf8FromWide(cmdline).c_str());
519 agentPid = pi.dwProcessId;
520 return OwnedHandle(pi.hProcess);
523 static void verifyPipeClientPid(HANDLE serverPipe, DWORD agentPid) {
524 const auto client = getNamedPipeClientProcessId(serverPipe);
525 const auto success = std::get<0>(client);
526 const auto lastError = std::get<2>(client);
527 if (success == GetNamedPipeClientProcessId_Result::Success) {
528 const auto clientPid = std::get<1>(client);
529 if (clientPid != agentPid) {
530 WStringBuilder errMsg;
531 errMsg << L"Security check failed: pipe client pid (" << clientPid
532 << L") does not match agent pid (" << agentPid << L")";
533 throwWinptyException(errMsg.c_str());
535 } else if (success == GetNamedPipeClientProcessId_Result::UnsupportedOs) {
536 trace("Pipe client PID security check skipped: "
537 "GetNamedPipeClientProcessId unsupported on this OS version");
539 throwWindowsError(L"GetNamedPipeClientProcessId failed", lastError);
543 static std::unique_ptr<winpty_t>
544 createAgentSession(const winpty_config_t *cfg,
545 const std::wstring &desktop,
546 const std::wstring ¶ms,
547 DWORD creationFlags) {
548 std::unique_ptr<winpty_t> wp(new winpty_t);
549 wp->agentTimeoutMs = cfg->timeoutMs;
550 wp->ioEvent = createEvent();
552 // Create control server pipe.
553 const auto pipeName =
554 L"\\\\.\\pipe\\winpty-control-" + GenRandom().uniqueName();
555 wp->controlPipe = createControlPipe(pipeName);
558 wp->agentProcess = startAgentProcess(
559 desktop, pipeName, params, creationFlags, agentPid);
560 connectControlPipe(*wp.get());
561 verifyPipeClientPid(wp->controlPipe.get(), agentPid);
563 return std::move(wp);
570 virtual std::wstring name() = 0;
571 virtual ~AgentDesktop() {}
574 class AgentDesktopDirect : public AgentDesktop {
576 AgentDesktopDirect(BackgroundDesktop &&desktop) :
577 m_desktop(std::move(desktop))
580 std::wstring name() override { return m_desktop.desktopName(); }
582 BackgroundDesktop m_desktop;
585 class AgentDesktopIndirect : public AgentDesktop {
587 AgentDesktopIndirect(std::unique_ptr<winpty_t> &&wp,
588 std::wstring &&desktopName) :
590 m_desktopName(std::move(desktopName))
593 std::wstring name() override { return m_desktopName; }
595 std::unique_ptr<winpty_t> m_wp;
596 std::wstring m_desktopName;
599 } // anonymous namespace
601 std::unique_ptr<AgentDesktop>
602 setupBackgroundDesktop(const winpty_config_t *cfg) {
603 bool useDesktopAgent =
604 !(cfg->flags & WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION);
605 const bool useDesktop = shouldCreateBackgroundDesktop(useDesktopAgent);
608 return std::unique_ptr<AgentDesktop>();
611 if (useDesktopAgent) {
612 auto wp = createAgentSession(
613 cfg, std::wstring(), L"--create-desktop", DETACHED_PROCESS);
615 // Read the desktop name.
616 auto packet = readPacket(*wp.get());
617 auto desktopName = packet.getWString();
620 if (desktopName.empty()) {
621 return std::unique_ptr<AgentDesktop>();
623 return std::unique_ptr<AgentDesktop>(
624 new AgentDesktopIndirect(std::move(wp),
625 std::move(desktopName)));
629 BackgroundDesktop desktop;
630 return std::unique_ptr<AgentDesktop>(new AgentDesktopDirect(
631 std::move(desktop)));
632 } catch (const WinptyException &e) {
633 trace("Error: failed to create background desktop, "
634 "using original desktop instead: %s",
635 utf8FromWide(e.what()).c_str());
636 return std::unique_ptr<AgentDesktop>();
641 WINPTY_API winpty_t *
642 winpty_open(const winpty_config_t *cfg,
643 winpty_error_ptr_t *err /*OPTIONAL*/) {
645 ASSERT(cfg != nullptr);
646 dumpWindowsVersion();
647 dumpVersionToTrace();
649 // Setup a background desktop for the agent.
650 auto desktop = setupBackgroundDesktop(cfg);
651 const auto desktopName = desktop ? desktop->name() : std::wstring();
653 // Start the primary agent session.
656 << cfg->flags << L' '
657 << cfg->mouseMode << L' '
659 << cfg->rows).str_moved();
660 auto wp = createAgentSession(cfg, desktopName, params,
663 // Close handles to the background desktop and restore the original
664 // window station. This must wait until we know the agent is running
665 // -- if we close these handles too soon, then the desktop and
666 // windowstation will be destroyed before the agent can connect with
669 // If we used a separate agent process to create the desktop, we
670 // disconnect from that process here, allowing it to exit.
673 // If we ran the agent process on a background desktop, then when we
674 // spawn a child process from the agent, it will need to be explicitly
675 // placed back onto the original desktop.
676 if (!desktopName.empty()) {
677 wp->spawnDesktopName = getCurrentDesktopName();
680 // Get the CONIN/CONOUT pipe names.
681 auto packet = readPacket(*wp.get());
682 wp->coninPipeName = packet.getWString();
683 wp->conoutPipeName = packet.getWString();
684 if (cfg->flags & WINPTY_FLAG_CONERR) {
685 wp->conerrPipeName = packet.getWString();
693 WINPTY_API HANDLE winpty_agent_process(winpty_t *wp) {
694 ASSERT(wp != nullptr);
695 return wp->agentProcess.get();
700 /*****************************************************************************
703 static const wchar_t *cstrFromWStringOrNull(const std::wstring &str) {
706 } catch (const std::bad_alloc&) {
711 WINPTY_API LPCWSTR winpty_conin_name(winpty_t *wp) {
712 ASSERT(wp != nullptr);
713 return cstrFromWStringOrNull(wp->coninPipeName);
716 WINPTY_API LPCWSTR winpty_conout_name(winpty_t *wp) {
717 ASSERT(wp != nullptr);
718 return cstrFromWStringOrNull(wp->conoutPipeName);
721 WINPTY_API LPCWSTR winpty_conerr_name(winpty_t *wp) {
722 ASSERT(wp != nullptr);
723 if (wp->conerrPipeName.empty()) {
726 return cstrFromWStringOrNull(wp->conerrPipeName);
732 /*****************************************************************************
733 * winpty agent RPC calls. */
737 // Close the control pipe if something goes wrong with the pipe communication,
738 // which could leave the control pipe in an inconsistent state.
741 RpcOperation(winpty_t &wp) : m_wp(wp) {
742 if (m_wp.controlPipe.get() == nullptr) {
743 throwWinptyException(L"Agent shutdown due to RPC failure");
748 trace("~RpcOperation: Closing control pipe");
749 m_wp.controlPipe.dispose(true);
752 void success() { m_success = true; }
755 bool m_success = false;
758 } // anonymous namespace
762 /*****************************************************************************
763 * winpty agent RPC call: process creation. */
765 // Return a std::wstring containing every character of the environment block.
766 // Typically, the block is non-empty, so the std::wstring returned ends with
767 // two NUL terminators. (These two terminators are counted in size(), so
768 // calling c_str() produces a triply-terminated string.)
769 static std::wstring wstringFromEnvBlock(const wchar_t *env) {
772 const wchar_t *p = env;
773 while (*p != L'\0') {
777 envStr.assign(env, p);
779 // Assuming the environment was non-empty, envStr now ends with two NUL
782 // If the environment were empty, though, then envStr would only be
783 // singly terminated, but the MSDN documentation thinks an env block is
784 // always doubly-terminated, so add an extra NUL just in case it
786 const auto envStrSz = envStr.size();
788 ASSERT(envStr[0] == L'\0');
789 envStr.push_back(L'\0');
791 ASSERT(envStrSz >= 3);
792 ASSERT(envStr[envStrSz - 3] != L'\0');
793 ASSERT(envStr[envStrSz - 2] == L'\0');
794 ASSERT(envStr[envStrSz - 1] == L'\0');
800 WINPTY_API winpty_spawn_config_t *
801 winpty_spawn_config_new(UINT64 winptyFlags,
802 LPCWSTR appname /*OPTIONAL*/,
803 LPCWSTR cmdline /*OPTIONAL*/,
804 LPCWSTR cwd /*OPTIONAL*/,
805 LPCWSTR env /*OPTIONAL*/,
806 winpty_error_ptr_t *err /*OPTIONAL*/) {
808 ASSERT((winptyFlags & WINPTY_SPAWN_FLAG_MASK) == winptyFlags);
809 std::unique_ptr<winpty_spawn_config_t> cfg(new winpty_spawn_config_t);
810 cfg->winptyFlags = winptyFlags;
811 if (appname != nullptr) { cfg->appname = appname; }
812 if (cmdline != nullptr) { cfg->cmdline = cmdline; }
813 if (cwd != nullptr) { cfg->cwd = cwd; }
814 if (env != nullptr) { cfg->env = wstringFromEnvBlock(env); }
815 return cfg.release();
819 WINPTY_API void winpty_spawn_config_free(winpty_spawn_config_t *cfg) {
823 // It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it
824 // back to 64-bits. See the MSDN article, "Interprocess Communication Between
825 // 32-bit and 64-bit Applications".
826 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx
827 static inline HANDLE handleFromInt64(int64_t i) {
828 return reinterpret_cast<HANDLE>(static_cast<intptr_t>(i));
831 // Given a process and a handle in that process, duplicate the handle into the
832 // current process and close it in the originating process.
833 static inline OwnedHandle stealHandle(HANDLE process, HANDLE handle) {
834 HANDLE result = nullptr;
835 if (!DuplicateHandle(process, handle,
838 DUPLICATE_CLOSE_SOURCE | DUPLICATE_SAME_ACCESS)) {
839 throwWindowsError(L"DuplicateHandle of process handle");
841 return OwnedHandle(result);
845 winpty_spawn(winpty_t *wp,
846 const winpty_spawn_config_t *cfg,
847 HANDLE *process_handle /*OPTIONAL*/,
848 HANDLE *thread_handle /*OPTIONAL*/,
849 DWORD *create_process_error /*OPTIONAL*/,
850 winpty_error_ptr_t *err /*OPTIONAL*/) {
852 ASSERT(wp != nullptr && cfg != nullptr);
854 if (process_handle != nullptr) { *process_handle = nullptr; }
855 if (thread_handle != nullptr) { *thread_handle = nullptr; }
856 if (create_process_error != nullptr) { *create_process_error = 0; }
858 LockGuard<Mutex> lock(wp->mutex);
859 RpcOperation rpc(*wp);
861 // Send spawn request.
862 auto packet = newPacket();
863 packet.putInt32(AgentMsg::StartProcess);
864 packet.putInt64(cfg->winptyFlags);
865 packet.putInt32(process_handle != nullptr);
866 packet.putInt32(thread_handle != nullptr);
867 packet.putWString(cfg->appname);
868 packet.putWString(cfg->cmdline);
869 packet.putWString(cfg->cwd);
870 packet.putWString(cfg->env);
871 packet.putWString(wp->spawnDesktopName);
872 writePacket(*wp, packet);
875 auto reply = readPacket(*wp);
876 const auto result = static_cast<StartProcessResult>(reply.getInt32());
877 if (result == StartProcessResult::CreateProcessFailed) {
878 const DWORD lastError = reply.getInt32();
880 if (create_process_error != nullptr) {
881 *create_process_error = lastError;
884 throw LibWinptyException(WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED,
885 L"CreateProcess failed");
886 } else if (result == StartProcessResult::ProcessCreated) {
887 const HANDLE remoteProcess = handleFromInt64(reply.getInt64());
888 const HANDLE remoteThread = handleFromInt64(reply.getInt64());
890 OwnedHandle localProcess;
891 OwnedHandle localThread;
892 if (remoteProcess != nullptr) {
894 stealHandle(wp->agentProcess.get(), remoteProcess);
896 if (remoteThread != nullptr) {
898 stealHandle(wp->agentProcess.get(), remoteThread);
900 if (process_handle != nullptr) {
901 *process_handle = localProcess.release();
903 if (thread_handle != nullptr) {
904 *thread_handle = localThread.release();
908 throwWinptyException(
909 L"Agent RPC error: invalid StartProcessResult");
917 /*****************************************************************************
918 * winpty agent RPC calls: everything else */
921 winpty_set_size(winpty_t *wp, int cols, int rows,
922 winpty_error_ptr_t *err /*OPTIONAL*/) {
924 ASSERT(wp != nullptr && cols > 0 && rows > 0);
925 LockGuard<Mutex> lock(wp->mutex);
926 RpcOperation rpc(*wp);
927 auto packet = newPacket();
928 packet.putInt32(AgentMsg::SetSize);
929 packet.putInt32(cols);
930 packet.putInt32(rows);
931 writePacket(*wp, packet);
932 readPacket(*wp).assertEof();
939 winpty_get_console_process_list(winpty_t *wp, int *processList, const int processCount,
940 winpty_error_ptr_t *err /*OPTIONAL*/) {
942 ASSERT(wp != nullptr);
943 ASSERT(processList != nullptr);
944 LockGuard<Mutex> lock(wp->mutex);
945 RpcOperation rpc(*wp);
946 auto packet = newPacket();
947 packet.putInt32(AgentMsg::GetConsoleProcessList);
948 writePacket(*wp, packet);
949 auto reply = readPacket(*wp);
951 auto actualProcessCount = reply.getInt32();
953 if (actualProcessCount <= processCount) {
954 for (auto i = 0; i < actualProcessCount; i++) {
955 processList[i] = reply.getInt32();
961 return actualProcessCount;
965 WINPTY_API void winpty_free(winpty_t *wp) {
966 // At least in principle, CloseHandle can fail, so this deletion can
967 // fail. It won't throw an exception, but maybe there's an error that
968 // should be propagated?