| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include "Agent.h" |
| |
|
| | #include <windows.h> |
| |
|
| | #include <stdint.h> |
| | #include <stdio.h> |
| | #include <stdlib.h> |
| | #include <string.h> |
| |
|
| | #include <string> |
| | #include <utility> |
| | #include <vector> |
| |
|
| | #include "../include/winpty_constants.h" |
| |
|
| | #include "../shared/AgentMsg.h" |
| | #include "../shared/Buffer.h" |
| | #include "../shared/DebugClient.h" |
| | #include "../shared/GenRandom.h" |
| | #include "../shared/StringBuilder.h" |
| | #include "../shared/StringUtil.h" |
| | #include "../shared/WindowsVersion.h" |
| | #include "../shared/WinptyAssert.h" |
| |
|
| | #include "ConsoleFont.h" |
| | #include "ConsoleInput.h" |
| | #include "NamedPipe.h" |
| | #include "Scraper.h" |
| | #include "Terminal.h" |
| | #include "Win32ConsoleBuffer.h" |
| |
|
| | namespace { |
| |
|
| | static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) |
| | { |
| | if (dwCtrlType == CTRL_C_EVENT) { |
| | |
| | return TRUE; |
| | } |
| | return FALSE; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | static void detectNewWindows10Console( |
| | Win32Console &console, Win32ConsoleBuffer &buffer) |
| | { |
| | if (!isAtLeastWindows8()) { |
| | return; |
| | } |
| |
|
| | ConsoleScreenBufferInfo info = buffer.bufferInfo(); |
| |
|
| | |
| | |
| | if (info.srWindow.Left == info.srWindow.Right && |
| | info.srWindow.Top == info.srWindow.Bottom) { |
| | trace("detectNewWindows10Console: Initial console window was 1x1 -- " |
| | "expanding for test"); |
| | setSmallFont(buffer.conout(), 400, false); |
| | buffer.moveWindow(SmallRect(0, 0, 1, 1)); |
| | buffer.resizeBuffer(Coord(400, 1)); |
| | buffer.moveWindow(SmallRect(0, 0, 2, 1)); |
| | |
| | |
| | |
| | |
| | const auto largest = GetLargestConsoleWindowSize(buffer.conout()); |
| | buffer.moveWindow( |
| | SmallRect(0, 0, std::min(largest.X, buffer.bufferSize().X), 1)); |
| | info = buffer.bufferInfo(); |
| | ASSERT(info.srWindow.Right > info.srWindow.Left && |
| | "Could not expand console window from 1x1"); |
| | } |
| |
|
| | |
| | const Coord initialPosition(info.srWindow.Right, info.srWindow.Bottom); |
| | buffer.setCursorPosition(initialPosition); |
| | ASSERT(!console.frozen()); |
| | console.setFreezeUsesMark(true); |
| | console.setFrozen(true); |
| | const bool isNewW10 = (buffer.cursorPosition() == initialPosition); |
| | console.setFrozen(false); |
| | buffer.setCursorPosition(Coord(0, 0)); |
| |
|
| | trace("Attempting to detect new Windows 10 console using MARK: %s", |
| | isNewW10 ? "detected" : "not detected"); |
| | console.setFreezeUsesMark(false); |
| | console.setNewW10(isNewW10); |
| | } |
| |
|
| | static inline WriteBuffer newPacket() { |
| | WriteBuffer packet; |
| | packet.putRawValue<uint64_t>(0); |
| | return packet; |
| | } |
| |
|
| | static HANDLE duplicateHandle(HANDLE h) { |
| | HANDLE ret = nullptr; |
| | if (!DuplicateHandle( |
| | GetCurrentProcess(), h, |
| | GetCurrentProcess(), &ret, |
| | 0, FALSE, DUPLICATE_SAME_ACCESS)) { |
| | ASSERT(false && "DuplicateHandle failed!"); |
| | } |
| | return ret; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | static int64_t int64FromHandle(HANDLE h) { |
| | return static_cast<int64_t>(reinterpret_cast<intptr_t>(h)); |
| | } |
| |
|
| | } |
| |
|
| | Agent::Agent(LPCWSTR controlPipeName, |
| | uint64_t agentFlags, |
| | int mouseMode, |
| | int initialCols, |
| | int initialRows) : |
| | m_useConerr((agentFlags & WINPTY_FLAG_CONERR) != 0), |
| | m_plainMode((agentFlags & WINPTY_FLAG_PLAIN_OUTPUT) != 0), |
| | m_mouseMode(mouseMode) |
| | { |
| | trace("Agent::Agent entered"); |
| |
|
| | ASSERT(initialCols >= 1 && initialRows >= 1); |
| | initialCols = std::min(initialCols, MAX_CONSOLE_WIDTH); |
| | initialRows = std::min(initialRows, MAX_CONSOLE_HEIGHT); |
| |
|
| | const bool outputColor = |
| | !m_plainMode || (agentFlags & WINPTY_FLAG_COLOR_ESCAPES); |
| | const Coord initialSize(initialCols, initialRows); |
| |
|
| | auto primaryBuffer = openPrimaryBuffer(); |
| | if (m_useConerr) { |
| | m_errorBuffer = Win32ConsoleBuffer::createErrorBuffer(); |
| | } |
| |
|
| | detectNewWindows10Console(m_console, *primaryBuffer); |
| |
|
| | m_controlPipe = &connectToControlPipe(controlPipeName); |
| | m_coninPipe = &createDataServerPipe(false, L"conin"); |
| | m_conoutPipe = &createDataServerPipe(true, L"conout"); |
| | if (m_useConerr) { |
| | m_conerrPipe = &createDataServerPipe(true, L"conerr"); |
| | } |
| |
|
| | |
| | { |
| | auto setupPacket = newPacket(); |
| | setupPacket.putWString(m_coninPipe->name()); |
| | setupPacket.putWString(m_conoutPipe->name()); |
| | if (m_useConerr) { |
| | setupPacket.putWString(m_conerrPipe->name()); |
| | } |
| | writePacket(setupPacket); |
| | } |
| |
|
| | std::unique_ptr<Terminal> primaryTerminal; |
| | primaryTerminal.reset(new Terminal(*m_conoutPipe, |
| | m_plainMode, |
| | outputColor)); |
| | m_primaryScraper.reset(new Scraper(m_console, |
| | *primaryBuffer, |
| | std::move(primaryTerminal), |
| | initialSize)); |
| | if (m_useConerr) { |
| | std::unique_ptr<Terminal> errorTerminal; |
| | errorTerminal.reset(new Terminal(*m_conerrPipe, |
| | m_plainMode, |
| | outputColor)); |
| | m_errorScraper.reset(new Scraper(m_console, |
| | *m_errorBuffer, |
| | std::move(errorTerminal), |
| | initialSize)); |
| | } |
| |
|
| | m_console.setTitle(m_currentTitle); |
| |
|
| | const HANDLE conin = GetStdHandle(STD_INPUT_HANDLE); |
| | m_consoleInput.reset( |
| | new ConsoleInput(conin, m_mouseMode, *this, m_console)); |
| |
|
| | |
| | |
| | |
| | |
| | SetConsoleCtrlHandler(NULL, FALSE); |
| | SetConsoleCtrlHandler(consoleCtrlHandler, TRUE); |
| |
|
| | setPollInterval(25); |
| | } |
| |
|
| | Agent::~Agent() |
| | { |
| | trace("Agent::~Agent entered"); |
| | agentShutdown(); |
| | if (m_childProcess != NULL) { |
| | CloseHandle(m_childProcess); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | void Agent::sendDsr() |
| | { |
| | if (!m_plainMode && !m_conoutPipe->isClosed()) { |
| | m_conoutPipe->write("\x1B[6n"); |
| | } |
| | } |
| |
|
| | NamedPipe &Agent::connectToControlPipe(LPCWSTR pipeName) |
| | { |
| | NamedPipe &pipe = createNamedPipe(); |
| | pipe.connectToServer(pipeName, NamedPipe::OpenMode::Duplex); |
| | pipe.setReadBufferSize(64 * 1024); |
| | return pipe; |
| | } |
| |
|
| | |
| | NamedPipe &Agent::createDataServerPipe(bool write, const wchar_t *kind) |
| | { |
| | const auto name = |
| | (WStringBuilder(128) |
| | << L"\\\\.\\pipe\\winpty-" |
| | << kind << L'-' |
| | << GenRandom().uniqueName()).str_moved(); |
| | NamedPipe &pipe = createNamedPipe(); |
| | pipe.openServerPipe( |
| | name.c_str(), |
| | write ? NamedPipe::OpenMode::Writing |
| | : NamedPipe::OpenMode::Reading, |
| | write ? 8192 : 0, |
| | write ? 0 : 256); |
| | if (!write) { |
| | pipe.setReadBufferSize(64 * 1024); |
| | } |
| | return pipe; |
| | } |
| |
|
| | void Agent::onPipeIo(NamedPipe &namedPipe) |
| | { |
| | if (&namedPipe == m_conoutPipe || &namedPipe == m_conerrPipe) { |
| | autoClosePipesForShutdown(); |
| | } else if (&namedPipe == m_coninPipe) { |
| | pollConinPipe(); |
| | } else if (&namedPipe == m_controlPipe) { |
| | pollControlPipe(); |
| | } |
| | } |
| |
|
| | void Agent::pollControlPipe() |
| | { |
| | if (m_controlPipe->isClosed()) { |
| | trace("Agent exiting (control pipe is closed)"); |
| | shutdown(); |
| | return; |
| | } |
| |
|
| | while (true) { |
| | uint64_t packetSize = 0; |
| | const auto amt1 = |
| | m_controlPipe->peek(&packetSize, sizeof(packetSize)); |
| | if (amt1 < sizeof(packetSize)) { |
| | break; |
| | } |
| | ASSERT(packetSize >= sizeof(packetSize) && packetSize <= SIZE_MAX); |
| | if (m_controlPipe->bytesAvailable() < packetSize) { |
| | if (m_controlPipe->readBufferSize() < packetSize) { |
| | m_controlPipe->setReadBufferSize(packetSize); |
| | } |
| | break; |
| | } |
| | std::vector<char> packetData; |
| | packetData.resize(packetSize); |
| | const auto amt2 = m_controlPipe->read(packetData.data(), packetSize); |
| | ASSERT(amt2 == packetSize); |
| | try { |
| | ReadBuffer buffer(std::move(packetData)); |
| | buffer.getRawValue<uint64_t>(); |
| | handlePacket(buffer); |
| | } catch (const ReadBuffer::DecodeError&) { |
| | ASSERT(false && "Decode error"); |
| | } |
| | } |
| | } |
| |
|
| | void Agent::handlePacket(ReadBuffer &packet) |
| | { |
| | const int type = packet.getInt32(); |
| | switch (type) { |
| | case AgentMsg::StartProcess: |
| | handleStartProcessPacket(packet); |
| | break; |
| | case AgentMsg::SetSize: |
| | |
| | |
| | |
| | |
| | |
| | handleSetSizePacket(packet); |
| | break; |
| | case AgentMsg::GetConsoleProcessList: |
| | handleGetConsoleProcessListPacket(packet); |
| | break; |
| | default: |
| | trace("Unrecognized message, id:%d", type); |
| | } |
| | } |
| |
|
| | void Agent::writePacket(WriteBuffer &packet) |
| | { |
| | const auto &bytes = packet.buf(); |
| | packet.replaceRawValue<uint64_t>(0, bytes.size()); |
| | m_controlPipe->write(bytes.data(), bytes.size()); |
| | } |
| |
|
| | void Agent::handleStartProcessPacket(ReadBuffer &packet) |
| | { |
| | ASSERT(m_childProcess == nullptr); |
| | ASSERT(!m_closingOutputPipes); |
| |
|
| | const uint64_t spawnFlags = packet.getInt64(); |
| | const bool wantProcessHandle = packet.getInt32() != 0; |
| | const bool wantThreadHandle = packet.getInt32() != 0; |
| | const auto program = packet.getWString(); |
| | const auto cmdline = packet.getWString(); |
| | const auto cwd = packet.getWString(); |
| | const auto env = packet.getWString(); |
| | const auto desktop = packet.getWString(); |
| | packet.assertEof(); |
| |
|
| | auto cmdlineV = vectorWithNulFromString(cmdline); |
| | auto desktopV = vectorWithNulFromString(desktop); |
| | auto envV = vectorFromString(env); |
| |
|
| | LPCWSTR programArg = program.empty() ? nullptr : program.c_str(); |
| | LPWSTR cmdlineArg = cmdline.empty() ? nullptr : cmdlineV.data(); |
| | LPCWSTR cwdArg = cwd.empty() ? nullptr : cwd.c_str(); |
| | LPWSTR envArg = env.empty() ? nullptr : envV.data(); |
| |
|
| | STARTUPINFOW sui = {}; |
| | PROCESS_INFORMATION pi = {}; |
| | sui.cb = sizeof(sui); |
| | sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data(); |
| | BOOL inheritHandles = FALSE; |
| | if (m_useConerr) { |
| | inheritHandles = TRUE; |
| | sui.dwFlags |= STARTF_USESTDHANDLES; |
| | sui.hStdInput = GetStdHandle(STD_INPUT_HANDLE); |
| | sui.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); |
| | sui.hStdError = m_errorBuffer->conout(); |
| | } |
| |
|
| | const BOOL success = |
| | CreateProcessW(programArg, cmdlineArg, nullptr, nullptr, |
| | inheritHandles, |
| | CREATE_UNICODE_ENVIRONMENT, |
| | envArg, cwdArg, &sui, &pi); |
| | const int lastError = success ? 0 : GetLastError(); |
| |
|
| | trace("CreateProcess: %s %u", |
| | (success ? "success" : "fail"), |
| | static_cast<unsigned int>(pi.dwProcessId)); |
| |
|
| | auto reply = newPacket(); |
| | if (success) { |
| | int64_t replyProcess = 0; |
| | int64_t replyThread = 0; |
| | if (wantProcessHandle) { |
| | replyProcess = int64FromHandle(duplicateHandle(pi.hProcess)); |
| | } |
| | if (wantThreadHandle) { |
| | replyThread = int64FromHandle(duplicateHandle(pi.hThread)); |
| | } |
| | CloseHandle(pi.hThread); |
| | m_childProcess = pi.hProcess; |
| | m_autoShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN) != 0; |
| | m_exitAfterShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN) != 0; |
| | reply.putInt32(static_cast<int32_t>(StartProcessResult::ProcessCreated)); |
| | reply.putInt64(replyProcess); |
| | reply.putInt64(replyThread); |
| | } else { |
| | reply.putInt32(static_cast<int32_t>(StartProcessResult::CreateProcessFailed)); |
| | reply.putInt32(lastError); |
| | } |
| | writePacket(reply); |
| | } |
| |
|
| | void Agent::handleSetSizePacket(ReadBuffer &packet) |
| | { |
| | const int cols = packet.getInt32(); |
| | const int rows = packet.getInt32(); |
| | packet.assertEof(); |
| | resizeWindow(cols, rows); |
| | auto reply = newPacket(); |
| | writePacket(reply); |
| | } |
| |
|
| | void Agent::handleGetConsoleProcessListPacket(ReadBuffer &packet) |
| | { |
| | packet.assertEof(); |
| |
|
| | auto processList = std::vector<DWORD>(64); |
| | auto processCount = GetConsoleProcessList(&processList[0], processList.size()); |
| | if (processList.size() < processCount) { |
| | processList.resize(processCount); |
| | processCount = GetConsoleProcessList(&processList[0], processList.size()); |
| | } |
| |
|
| | if (processCount == 0) { |
| | trace("GetConsoleProcessList failed"); |
| | } |
| |
|
| | auto reply = newPacket(); |
| | reply.putInt32(processCount); |
| | for (DWORD i = 0; i < processCount; i++) { |
| | reply.putInt32(processList[i]); |
| | } |
| | writePacket(reply); |
| | } |
| |
|
| | void Agent::pollConinPipe() |
| | { |
| | const std::string newData = m_coninPipe->readAllToString(); |
| | if (hasDebugFlag("input_separated_bytes")) { |
| | |
| | |
| | |
| | for (size_t i = 0; i < newData.size(); ++i) { |
| | m_consoleInput->writeInput(newData.substr(i, 1)); |
| | } |
| | } else { |
| | m_consoleInput->writeInput(newData); |
| | } |
| | } |
| |
|
| | void Agent::onPollTimeout() |
| | { |
| | m_consoleInput->updateInputFlags(); |
| | const bool enableMouseMode = m_consoleInput->shouldActivateTerminalMouse(); |
| |
|
| | |
| | |
| | m_consoleInput->flushIncompleteEscapeCode(); |
| |
|
| | const bool shouldScrapeContent = !m_closingOutputPipes; |
| |
|
| | |
| | if (m_autoShutdown && |
| | m_childProcess != nullptr && |
| | WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) { |
| | CloseHandle(m_childProcess); |
| | m_childProcess = nullptr; |
| |
|
| | |
| | |
| | |
| | m_closingOutputPipes = true; |
| | } |
| |
|
| | |
| | |
| | if (shouldScrapeContent) { |
| | syncConsoleTitle(); |
| | scrapeBuffers(); |
| | } |
| |
|
| | |
| | |
| | m_primaryScraper->terminal().enableMouseMode( |
| | enableMouseMode && !m_closingOutputPipes); |
| |
|
| | autoClosePipesForShutdown(); |
| | } |
| |
|
| | void Agent::autoClosePipesForShutdown() |
| | { |
| | if (m_closingOutputPipes) { |
| | |
| | |
| | |
| | if (m_conoutPipe->isConnected() && |
| | m_conoutPipe->bytesToSend() == 0) { |
| | trace("Closing CONOUT pipe (auto-shutdown)"); |
| | m_conoutPipe->closePipe(); |
| | } |
| | if (m_conerrPipe != nullptr && |
| | m_conerrPipe->isConnected() && |
| | m_conerrPipe->bytesToSend() == 0) { |
| | trace("Closing CONERR pipe (auto-shutdown)"); |
| | m_conerrPipe->closePipe(); |
| | } |
| | if (m_exitAfterShutdown && |
| | m_conoutPipe->isClosed() && |
| | (m_conerrPipe == nullptr || m_conerrPipe->isClosed())) { |
| | trace("Agent exiting (exit-after-shutdown)"); |
| | shutdown(); |
| | } |
| | } |
| | } |
| |
|
| | std::unique_ptr<Win32ConsoleBuffer> Agent::openPrimaryBuffer() |
| | { |
| | |
| | |
| | |
| | |
| | |
| | if (!m_useConerr) { |
| | return Win32ConsoleBuffer::openConout(); |
| | } else { |
| | return Win32ConsoleBuffer::openStdout(); |
| | } |
| | } |
| |
|
| | void Agent::resizeWindow(int cols, int rows) |
| | { |
| | ASSERT(cols >= 1 && rows >= 1); |
| | cols = std::min(cols, MAX_CONSOLE_WIDTH); |
| | rows = std::min(rows, MAX_CONSOLE_HEIGHT); |
| |
|
| | Win32Console::FreezeGuard guard(m_console, m_console.frozen()); |
| | const Coord newSize(cols, rows); |
| | ConsoleScreenBufferInfo info; |
| | auto primaryBuffer = openPrimaryBuffer(); |
| | m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info); |
| | m_consoleInput->setMouseWindowRect(info.windowRect()); |
| | if (m_errorScraper) { |
| | m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info); |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | INPUT_RECORD sizeEvent {}; |
| | sizeEvent.EventType = WINDOW_BUFFER_SIZE_EVENT; |
| | sizeEvent.Event.WindowBufferSizeEvent.dwSize = primaryBuffer->bufferSize(); |
| | DWORD actual {}; |
| | WriteConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &sizeEvent, 1, &actual); |
| | } |
| |
|
| | void Agent::scrapeBuffers() |
| | { |
| | Win32Console::FreezeGuard guard(m_console, m_console.frozen()); |
| | ConsoleScreenBufferInfo info; |
| | m_primaryScraper->scrapeBuffer(*openPrimaryBuffer(), info); |
| | m_consoleInput->setMouseWindowRect(info.windowRect()); |
| | if (m_errorScraper) { |
| | m_errorScraper->scrapeBuffer(*m_errorBuffer, info); |
| | } |
| | } |
| |
|
| | void Agent::syncConsoleTitle() |
| | { |
| | std::wstring newTitle = m_console.title(); |
| | if (newTitle != m_currentTitle) { |
| | std::string command = std::string("\x1b]0;") + |
| | utf8FromWide(newTitle) + "\x07"; |
| | m_conoutPipe->write(command.c_str()); |
| | m_currentTitle = newTitle; |
| | } |
| | } |
| |
|