installed pty
[VSoRC/.git] / node_modules / node-pty / deps / winpty / src / agent / Agent.cc
1 // Copyright (c) 2011-2015 Ryan Prichard
2 //
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:
9 //
10 // The above copyright notice and this permission notice shall be included in
11 // all copies or substantial portions of the Software.
12 //
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
19 // IN THE SOFTWARE.
20
21 #include "Agent.h"
22
23 #include <windows.h>
24
25 #include <stdint.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29
30 #include <string>
31 #include <utility>
32 #include <vector>
33
34 #include "../include/winpty_constants.h"
35
36 #include "../shared/AgentMsg.h"
37 #include "../shared/Buffer.h"
38 #include "../shared/DebugClient.h"
39 #include "../shared/GenRandom.h"
40 #include "../shared/StringBuilder.h"
41 #include "../shared/StringUtil.h"
42 #include "../shared/WindowsVersion.h"
43 #include "../shared/WinptyAssert.h"
44
45 #include "ConsoleFont.h"
46 #include "ConsoleInput.h"
47 #include "NamedPipe.h"
48 #include "Scraper.h"
49 #include "Terminal.h"
50 #include "Win32ConsoleBuffer.h"
51
52 namespace {
53
54 static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType)
55 {
56     if (dwCtrlType == CTRL_C_EVENT) {
57         // Do nothing and claim to have handled the event.
58         return TRUE;
59     }
60     return FALSE;
61 }
62
63 // We can detect the new Windows 10 console by observing the effect of the
64 // Mark command.  In older consoles, Mark temporarily moves the cursor to the
65 // top-left of the console window.  In the new console, the cursor isn't
66 // initially moved.
67 //
68 // We might like to use Mark to freeze the console, but we can't, because when
69 // the Mark command ends, the console moves the cursor back to its starting
70 // point, even if the console application has moved it in the meantime.
71 static void detectNewWindows10Console(
72         Win32Console &console, Win32ConsoleBuffer &buffer)
73 {
74     if (!isAtLeastWindows8()) {
75         return;
76     }
77
78     ConsoleScreenBufferInfo info = buffer.bufferInfo();
79
80     // Make sure the window isn't 1x1.  AFAIK, this should never happen
81     // accidentally.  It is difficult to make it happen deliberately.
82     if (info.srWindow.Left == info.srWindow.Right &&
83             info.srWindow.Top == info.srWindow.Bottom) {
84         trace("detectNewWindows10Console: Initial console window was 1x1 -- "
85               "expanding for test");
86         setSmallFont(buffer.conout(), 400, false);
87         buffer.moveWindow(SmallRect(0, 0, 1, 1));
88         buffer.resizeBuffer(Coord(400, 1));
89         buffer.moveWindow(SmallRect(0, 0, 2, 1));
90         // This use of GetLargestConsoleWindowSize ought to be unnecessary
91         // given the behavior I've seen from moveWindow(0, 0, 1, 1), but
92         // I'd like to be especially sure, considering that this code will
93         // rarely be tested.
94         const auto largest = GetLargestConsoleWindowSize(buffer.conout());
95         buffer.moveWindow(
96             SmallRect(0, 0, std::min(largest.X, buffer.bufferSize().X), 1));
97         info = buffer.bufferInfo();
98         ASSERT(info.srWindow.Right > info.srWindow.Left &&
99             "Could not expand console window from 1x1");
100     }
101
102     // Test whether MARK moves the cursor.
103     const Coord initialPosition(info.srWindow.Right, info.srWindow.Bottom);
104     buffer.setCursorPosition(initialPosition);
105     ASSERT(!console.frozen());
106     console.setFreezeUsesMark(true);
107     console.setFrozen(true);
108     const bool isNewW10 = (buffer.cursorPosition() == initialPosition);
109     console.setFrozen(false);
110     buffer.setCursorPosition(Coord(0, 0));
111
112     trace("Attempting to detect new Windows 10 console using MARK: %s",
113         isNewW10 ? "detected" : "not detected");
114     console.setFreezeUsesMark(false);
115     console.setNewW10(isNewW10);
116 }
117
118 static inline WriteBuffer newPacket() {
119     WriteBuffer packet;
120     packet.putRawValue<uint64_t>(0); // Reserve space for size.
121     return packet;
122 }
123
124 static HANDLE duplicateHandle(HANDLE h) {
125     HANDLE ret = nullptr;
126     if (!DuplicateHandle(
127             GetCurrentProcess(), h,
128             GetCurrentProcess(), &ret,
129             0, FALSE, DUPLICATE_SAME_ACCESS)) {
130         ASSERT(false && "DuplicateHandle failed!");
131     }
132     return ret;
133 }
134
135 // It's safe to truncate a handle from 64-bits to 32-bits, or to sign-extend it
136 // back to 64-bits.  See the MSDN article, "Interprocess Communication Between
137 // 32-bit and 64-bit Applications".
138 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa384203.aspx
139 static int64_t int64FromHandle(HANDLE h) {
140     return static_cast<int64_t>(reinterpret_cast<intptr_t>(h));
141 }
142
143 } // anonymous namespace
144
145 Agent::Agent(LPCWSTR controlPipeName,
146              uint64_t agentFlags,
147              int mouseMode,
148              int initialCols,
149              int initialRows) :
150     m_useConerr((agentFlags & WINPTY_FLAG_CONERR) != 0),
151     m_plainMode((agentFlags & WINPTY_FLAG_PLAIN_OUTPUT) != 0),
152     m_mouseMode(mouseMode)
153 {
154     trace("Agent::Agent entered");
155
156     ASSERT(initialCols >= 1 && initialRows >= 1);
157     initialCols = std::min(initialCols, MAX_CONSOLE_WIDTH);
158     initialRows = std::min(initialRows, MAX_CONSOLE_HEIGHT);
159
160     const bool outputColor =
161         !m_plainMode || (agentFlags & WINPTY_FLAG_COLOR_ESCAPES);
162     const Coord initialSize(initialCols, initialRows);
163
164     auto primaryBuffer = openPrimaryBuffer();
165     if (m_useConerr) {
166         m_errorBuffer = Win32ConsoleBuffer::createErrorBuffer();
167     }
168
169     detectNewWindows10Console(m_console, *primaryBuffer);
170
171     m_controlPipe = &connectToControlPipe(controlPipeName);
172     m_coninPipe = &createDataServerPipe(false, L"conin");
173     m_conoutPipe = &createDataServerPipe(true, L"conout");
174     if (m_useConerr) {
175         m_conerrPipe = &createDataServerPipe(true, L"conerr");
176     }
177
178     // Send an initial response packet to winpty.dll containing pipe names.
179     {
180         auto setupPacket = newPacket();
181         setupPacket.putWString(m_coninPipe->name());
182         setupPacket.putWString(m_conoutPipe->name());
183         if (m_useConerr) {
184             setupPacket.putWString(m_conerrPipe->name());
185         }
186         writePacket(setupPacket);
187     }
188
189     std::unique_ptr<Terminal> primaryTerminal;
190     primaryTerminal.reset(new Terminal(*m_conoutPipe,
191                                        m_plainMode,
192                                        outputColor));
193     m_primaryScraper.reset(new Scraper(m_console,
194                                        *primaryBuffer,
195                                        std::move(primaryTerminal),
196                                        initialSize));
197     if (m_useConerr) {
198         std::unique_ptr<Terminal> errorTerminal;
199         errorTerminal.reset(new Terminal(*m_conerrPipe,
200                                          m_plainMode,
201                                          outputColor));
202         m_errorScraper.reset(new Scraper(m_console,
203                                          *m_errorBuffer,
204                                          std::move(errorTerminal),
205                                          initialSize));
206     }
207
208     m_console.setTitle(m_currentTitle);
209
210     const HANDLE conin = GetStdHandle(STD_INPUT_HANDLE);
211     m_consoleInput.reset(
212         new ConsoleInput(conin, m_mouseMode, *this, m_console));
213
214     // Setup Ctrl-C handling.  First restore default handling of Ctrl-C.  This
215     // attribute is inherited by child processes.  Then register a custom
216     // Ctrl-C handler that does nothing.  The handler will be called when the
217     // agent calls GenerateConsoleCtrlEvent.
218     SetConsoleCtrlHandler(NULL, FALSE);
219     SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);
220
221     setPollInterval(25);
222 }
223
224 Agent::~Agent()
225 {
226     trace("Agent::~Agent entered");
227     agentShutdown();
228     if (m_childProcess != NULL) {
229         CloseHandle(m_childProcess);
230     }
231 }
232
233 // Write a "Device Status Report" command to the terminal.  The terminal will
234 // reply with a row+col escape sequence.  Presumably, the DSR reply will not
235 // split a keypress escape sequence, so it should be safe to assume that the
236 // bytes before it are complete keypresses.
237 void Agent::sendDsr()
238 {
239     if (!m_plainMode && !m_conoutPipe->isClosed()) {
240         m_conoutPipe->write("\x1B[6n");
241     }
242 }
243
244 NamedPipe &Agent::connectToControlPipe(LPCWSTR pipeName)
245 {
246     NamedPipe &pipe = createNamedPipe();
247     pipe.connectToServer(pipeName, NamedPipe::OpenMode::Duplex);
248     pipe.setReadBufferSize(64 * 1024);
249     return pipe;
250 }
251
252 // Returns a new server named pipe.  It has not yet been connected.
253 NamedPipe &Agent::createDataServerPipe(bool write, const wchar_t *kind)
254 {
255     const auto name =
256         (WStringBuilder(128)
257             << L"\\\\.\\pipe\\winpty-"
258             << kind << L'-'
259             << GenRandom().uniqueName()).str_moved();
260     NamedPipe &pipe = createNamedPipe();
261     pipe.openServerPipe(
262         name.c_str(),
263         write ? NamedPipe::OpenMode::Writing
264               : NamedPipe::OpenMode::Reading,
265         write ? 8192 : 0,
266         write ? 0 : 256);
267     if (!write) {
268         pipe.setReadBufferSize(64 * 1024);
269     }
270     return pipe;
271 }
272
273 void Agent::onPipeIo(NamedPipe &namedPipe)
274 {
275     if (&namedPipe == m_conoutPipe || &namedPipe == m_conerrPipe) {
276         autoClosePipesForShutdown();
277     } else if (&namedPipe == m_coninPipe) {
278         pollConinPipe();
279     } else if (&namedPipe == m_controlPipe) {
280         pollControlPipe();
281     }
282 }
283
284 void Agent::pollControlPipe()
285 {
286     if (m_controlPipe->isClosed()) {
287         trace("Agent exiting (control pipe is closed)");
288         shutdown();
289         return;
290     }
291
292     while (true) {
293         uint64_t packetSize = 0;
294         const auto amt1 =
295             m_controlPipe->peek(&packetSize, sizeof(packetSize));
296         if (amt1 < sizeof(packetSize)) {
297             break;
298         }
299         ASSERT(packetSize >= sizeof(packetSize) && packetSize <= SIZE_MAX);
300         if (m_controlPipe->bytesAvailable() < packetSize) {
301             if (m_controlPipe->readBufferSize() < packetSize) {
302                 m_controlPipe->setReadBufferSize(packetSize);
303             }
304             break;
305         }
306         std::vector<char> packetData;
307         packetData.resize(packetSize);
308         const auto amt2 = m_controlPipe->read(packetData.data(), packetSize);
309         ASSERT(amt2 == packetSize);
310         try {
311             ReadBuffer buffer(std::move(packetData));
312             buffer.getRawValue<uint64_t>(); // Discard the size.
313             handlePacket(buffer);
314         } catch (const ReadBuffer::DecodeError&) {
315             ASSERT(false && "Decode error");
316         }
317     }
318 }
319
320 void Agent::handlePacket(ReadBuffer &packet)
321 {
322     const int type = packet.getInt32();
323     switch (type) {
324     case AgentMsg::StartProcess:
325         handleStartProcessPacket(packet);
326         break;
327     case AgentMsg::SetSize:
328         // TODO: I think it might make sense to collapse consecutive SetSize
329         // messages.  i.e. The terminal process can probably generate SetSize
330         // messages faster than they can be processed, and some GUIs might
331         // generate a flood of them, so if we can read multiple SetSize packets
332         // at once, we can ignore the early ones.
333         handleSetSizePacket(packet);
334         break;
335     case AgentMsg::GetConsoleProcessList:
336         handleGetConsoleProcessListPacket(packet);
337         break;
338     default:
339         trace("Unrecognized message, id:%d", type);
340     }
341 }
342
343 void Agent::writePacket(WriteBuffer &packet)
344 {
345     const auto &bytes = packet.buf();
346     packet.replaceRawValue<uint64_t>(0, bytes.size());
347     m_controlPipe->write(bytes.data(), bytes.size());
348 }
349
350 void Agent::handleStartProcessPacket(ReadBuffer &packet)
351 {
352     ASSERT(m_childProcess == nullptr);
353     ASSERT(!m_closingOutputPipes);
354
355     const uint64_t spawnFlags = packet.getInt64();
356     const bool wantProcessHandle = packet.getInt32() != 0;
357     const bool wantThreadHandle = packet.getInt32() != 0;
358     const auto program = packet.getWString();
359     const auto cmdline = packet.getWString();
360     const auto cwd = packet.getWString();
361     const auto env = packet.getWString();
362     const auto desktop = packet.getWString();
363     packet.assertEof();
364
365     auto cmdlineV = vectorWithNulFromString(cmdline);
366     auto desktopV = vectorWithNulFromString(desktop);
367     auto envV = vectorFromString(env);
368
369     LPCWSTR programArg = program.empty() ? nullptr : program.c_str();
370     LPWSTR cmdlineArg = cmdline.empty() ? nullptr : cmdlineV.data();
371     LPCWSTR cwdArg = cwd.empty() ? nullptr : cwd.c_str();
372     LPWSTR envArg = env.empty() ? nullptr : envV.data();
373
374     STARTUPINFOW sui = {};
375     PROCESS_INFORMATION pi = {};
376     sui.cb = sizeof(sui);
377     sui.lpDesktop = desktop.empty() ? nullptr : desktopV.data();
378     BOOL inheritHandles = FALSE;
379     if (m_useConerr) {
380         inheritHandles = TRUE;
381         sui.dwFlags |= STARTF_USESTDHANDLES;
382         sui.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
383         sui.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
384         sui.hStdError = m_errorBuffer->conout();
385     }
386
387     const BOOL success =
388         CreateProcessW(programArg, cmdlineArg, nullptr, nullptr,
389                        /*bInheritHandles=*/inheritHandles,
390                        /*dwCreationFlags=*/CREATE_UNICODE_ENVIRONMENT,
391                        envArg, cwdArg, &sui, &pi);
392     const int lastError = success ? 0 : GetLastError();
393
394     trace("CreateProcess: %s %u",
395           (success ? "success" : "fail"),
396           static_cast<unsigned int>(pi.dwProcessId));
397
398     auto reply = newPacket();
399     if (success) {
400         int64_t replyProcess = 0;
401         int64_t replyThread = 0;
402         if (wantProcessHandle) {
403             replyProcess = int64FromHandle(duplicateHandle(pi.hProcess));
404         }
405         if (wantThreadHandle) {
406             replyThread = int64FromHandle(duplicateHandle(pi.hThread));
407         }
408         CloseHandle(pi.hThread);
409         m_childProcess = pi.hProcess;
410         m_autoShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN) != 0;
411         m_exitAfterShutdown = (spawnFlags & WINPTY_SPAWN_FLAG_EXIT_AFTER_SHUTDOWN) != 0;
412         reply.putInt32(static_cast<int32_t>(StartProcessResult::ProcessCreated));
413         reply.putInt64(replyProcess);
414         reply.putInt64(replyThread);
415     } else {
416         reply.putInt32(static_cast<int32_t>(StartProcessResult::CreateProcessFailed));
417         reply.putInt32(lastError);
418     }
419     writePacket(reply);
420 }
421
422 void Agent::handleSetSizePacket(ReadBuffer &packet)
423 {
424     const int cols = packet.getInt32();
425     const int rows = packet.getInt32();
426     packet.assertEof();
427     resizeWindow(cols, rows);
428     auto reply = newPacket();
429     writePacket(reply);
430 }
431
432 void Agent::handleGetConsoleProcessListPacket(ReadBuffer &packet)
433 {
434     packet.assertEof();
435
436     auto processList = std::vector<DWORD>(64);
437     auto processCount = GetConsoleProcessList(&processList[0], processList.size());
438     if (processList.size() < processCount) {
439         processList.resize(processCount);
440         processCount = GetConsoleProcessList(&processList[0], processList.size());
441     }
442
443     if (processCount == 0) {
444         trace("GetConsoleProcessList failed");
445     }
446
447     auto reply = newPacket();
448     reply.putInt32(processCount);
449     for (DWORD i = 0; i < processCount; i++) {
450         reply.putInt32(processList[i]);
451     }
452     writePacket(reply);
453 }
454
455 void Agent::pollConinPipe()
456 {
457     const std::string newData = m_coninPipe->readAllToString();
458     if (hasDebugFlag("input_separated_bytes")) {
459         // This debug flag is intended to help with testing incomplete escape
460         // sequences and multibyte UTF-8 encodings.  (I wonder if the normal
461         // code path ought to advance a state machine one byte at a time.)
462         for (size_t i = 0; i < newData.size(); ++i) {
463             m_consoleInput->writeInput(newData.substr(i, 1));
464         }
465     } else {
466         m_consoleInput->writeInput(newData);
467     }
468 }
469
470 void Agent::onPollTimeout()
471 {
472     m_consoleInput->updateInputFlags();
473     const bool enableMouseMode = m_consoleInput->shouldActivateTerminalMouse();
474
475     // Give the ConsoleInput object a chance to flush input from an incomplete
476     // escape sequence (e.g. pressing ESC).
477     m_consoleInput->flushIncompleteEscapeCode();
478
479     const bool shouldScrapeContent = !m_closingOutputPipes;
480
481     // Check if the child process has exited.
482     if (m_autoShutdown &&
483             m_childProcess != nullptr &&
484             WaitForSingleObject(m_childProcess, 0) == WAIT_OBJECT_0) {
485         CloseHandle(m_childProcess);
486         m_childProcess = nullptr;
487
488         // Close the data socket to signal to the client that the child
489         // process has exited.  If there's any data left to send, send it
490         // before closing the socket.
491         m_closingOutputPipes = true;
492     }
493
494     // Scrape for output *after* the above exit-check to ensure that we collect
495     // the child process's final output.
496     if (shouldScrapeContent) {
497         syncConsoleTitle();
498         scrapeBuffers();
499     }
500
501     // We must ensure that we disable mouse mode before closing the CONOUT
502     // pipe, so update the mouse mode here.
503     m_primaryScraper->terminal().enableMouseMode(
504         enableMouseMode && !m_closingOutputPipes);
505
506     autoClosePipesForShutdown();
507 }
508
509 void Agent::autoClosePipesForShutdown()
510 {
511     if (m_closingOutputPipes) {
512         // We don't want to close a pipe before it's connected!  If we do, the
513         // libwinpty client may try to connect to a non-existent pipe.  This
514         // case is important for short-lived programs.
515         if (m_conoutPipe->isConnected() &&
516                 m_conoutPipe->bytesToSend() == 0) {
517             trace("Closing CONOUT pipe (auto-shutdown)");
518             m_conoutPipe->closePipe();
519         }
520         if (m_conerrPipe != nullptr &&
521                 m_conerrPipe->isConnected() &&
522                 m_conerrPipe->bytesToSend() == 0) {
523             trace("Closing CONERR pipe (auto-shutdown)");
524             m_conerrPipe->closePipe();
525         }
526         if (m_exitAfterShutdown &&
527                 m_conoutPipe->isClosed() &&
528                 (m_conerrPipe == nullptr || m_conerrPipe->isClosed())) {
529             trace("Agent exiting (exit-after-shutdown)");
530             shutdown();
531         }
532     }
533 }
534
535 std::unique_ptr<Win32ConsoleBuffer> Agent::openPrimaryBuffer()
536 {
537     // If we're using a separate buffer for stderr, and a program were to
538     // activate the stderr buffer, then we could accidentally scrape the same
539     // buffer twice.  That probably shouldn't happen in ordinary use, but it
540     // can be avoided anyway by using the original console screen buffer in
541     // that mode.
542     if (!m_useConerr) {
543         return Win32ConsoleBuffer::openConout();
544     } else {
545         return Win32ConsoleBuffer::openStdout();
546     }
547 }
548
549 void Agent::resizeWindow(int cols, int rows)
550 {
551     ASSERT(cols >= 1 && rows >= 1);
552     cols = std::min(cols, MAX_CONSOLE_WIDTH);
553     rows = std::min(rows, MAX_CONSOLE_HEIGHT);
554
555     Win32Console::FreezeGuard guard(m_console, m_console.frozen());
556     const Coord newSize(cols, rows);
557     ConsoleScreenBufferInfo info;
558     auto primaryBuffer = openPrimaryBuffer();
559     m_primaryScraper->resizeWindow(*primaryBuffer, newSize, info);
560     m_consoleInput->setMouseWindowRect(info.windowRect());
561     if (m_errorScraper) {
562         m_errorScraper->resizeWindow(*m_errorBuffer, newSize, info);
563     }
564
565     // Synthesize a WINDOW_BUFFER_SIZE_EVENT event.  Normally, Windows
566     // generates this event only when the buffer size changes, not when the
567     // window size changes.  This behavior is undesirable in two ways:
568     //  - When winpty expands the window horizontally, it must expand the
569     //    buffer first, then the window.  At least some programs (e.g. the WSL
570     //    bash.exe wrapper) use the window width rather than the buffer width,
571     //    so there is a short timespan during which they can read the wrong
572     //    value.
573     //  - If the window's vertical size is changed, no event is generated,
574     //    even though a typical well-behaved console program cares about the
575     //    *window* height, not the *buffer* height.
576     // This synthesization works around a design flaw in the console.  It's probably
577     // harmless.  See https://github.com/rprichard/winpty/issues/110.
578     INPUT_RECORD sizeEvent {};
579     sizeEvent.EventType = WINDOW_BUFFER_SIZE_EVENT;
580     sizeEvent.Event.WindowBufferSizeEvent.dwSize = primaryBuffer->bufferSize();
581     DWORD actual {};
582     WriteConsoleInputW(GetStdHandle(STD_INPUT_HANDLE), &sizeEvent, 1, &actual);
583 }
584
585 void Agent::scrapeBuffers()
586 {
587     Win32Console::FreezeGuard guard(m_console, m_console.frozen());
588     ConsoleScreenBufferInfo info;
589     m_primaryScraper->scrapeBuffer(*openPrimaryBuffer(), info);
590     m_consoleInput->setMouseWindowRect(info.windowRect());
591     if (m_errorScraper) {
592         m_errorScraper->scrapeBuffer(*m_errorBuffer, info);
593     }
594 }
595
596 void Agent::syncConsoleTitle()
597 {
598     std::wstring newTitle = m_console.title();
599     if (newTitle != m_currentTitle) {
600         std::string command = std::string("\x1b]0;") +
601                 utf8FromWide(newTitle) + "\x07";
602         m_conoutPipe->write(command.c_str());
603         m_currentTitle = newTitle;
604     }
605 }