installed pty
[VSoRC/.git] / node_modules / node-pty / deps / winpty / src / unix-adapter / main.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 // MSYS's sys/cygwin.h header only declares cygwin_internal if WINVER is
22 // defined, which is defined in windows.h.  Therefore, include windows.h early.
23 #include <windows.h>
24
25 #include <assert.h>
26 #include <cygwin/version.h>
27 #include <errno.h>
28 #include <signal.h>
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <string.h>
32 #include <sys/ioctl.h>
33 #include <sys/select.h>
34 #include <sys/cygwin.h>
35 #include <termios.h>
36 #include <unistd.h>
37
38 #include <map>
39 #include <string>
40 #include <utility>
41 #include <vector>
42
43 #include <winpty.h>
44 #include "../shared/DebugClient.h"
45 #include "../shared/UnixCtrlChars.h"
46 #include "../shared/WinptyVersion.h"
47 #include "InputHandler.h"
48 #include "OutputHandler.h"
49 #include "Util.h"
50 #include "WakeupFd.h"
51
52 #define CSI "\x1b["
53
54 static WakeupFd *g_mainWakeup = NULL;
55
56 static WakeupFd &mainWakeup()
57 {
58     if (g_mainWakeup == NULL) {
59         static const char msg[] = "Internal error: g_mainWakeup is NULL\r\n";
60         write(STDERR_FILENO, msg, sizeof(msg) - 1);
61         abort();
62     }
63     return *g_mainWakeup;
64 }
65
66 struct SavedTermiosMode {
67     int count;
68     bool valid[3];
69     termios mode[3];
70 };
71
72 // Put the input terminal into non-canonical mode.
73 static SavedTermiosMode setRawTerminalMode(
74     bool allowNonTtys, bool setStdout, bool setStderr)
75 {
76     SavedTermiosMode ret;
77     const char *const kNames[3] = { "stdin", "stdout", "stderr" };
78
79     ret.valid[0] = true;
80     ret.valid[1] = setStdout;
81     ret.valid[2] = setStderr;
82
83     for (int i = 0; i < 3; ++i) {
84         if (!ret.valid[i]) {
85             continue;
86         }
87         if (!isatty(i)) {
88             ret.valid[i] = false;
89             if (!allowNonTtys) {
90                 fprintf(stderr, "%s is not a tty\n", kNames[i]);
91                 exit(1);
92             }
93         } else {
94             ret.valid[i] = true;
95             if (tcgetattr(i, &ret.mode[i]) < 0) {
96                 perror("tcgetattr failed");
97                 exit(1);
98             }
99         }
100     }
101
102     if (ret.valid[STDIN_FILENO]) {
103         termios buf;
104         if (tcgetattr(STDIN_FILENO, &buf) < 0) {
105             perror("tcgetattr failed");
106             exit(1);
107         }
108         buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
109         buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
110         buf.c_cflag &= ~(CSIZE | PARENB);
111         buf.c_cflag |= CS8;
112         buf.c_cc[VMIN] = 1;  // blocking read
113         buf.c_cc[VTIME] = 0;
114         if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) {
115             fprintf(stderr, "tcsetattr failed\n");
116             exit(1);
117         }
118     }
119
120     for (int i = STDOUT_FILENO; i <= STDERR_FILENO; ++i) {
121         if (!ret.valid[i]) {
122             continue;
123         }
124         termios buf;
125         if (tcgetattr(i, &buf) < 0) {
126             perror("tcgetattr failed");
127             exit(1);
128         }
129         buf.c_cflag &= ~(CSIZE | PARENB);
130         buf.c_cflag |= CS8;
131         buf.c_oflag &= ~OPOST;
132         if (tcsetattr(i, TCSAFLUSH, &buf) < 0) {
133             fprintf(stderr, "tcsetattr failed\n");
134             exit(1);
135         }
136     }
137
138     return ret;
139 }
140
141 static void restoreTerminalMode(const SavedTermiosMode &original)
142 {
143     for (int i = 0; i < 3; ++i) {
144         if (!original.valid[i]) {
145             continue;
146         }
147         if (tcsetattr(i, TCSAFLUSH, &original.mode[i]) < 0) {
148             perror("error restoring terminal mode");
149             exit(1);
150         }
151     }
152 }
153
154 static void debugShowKey(bool allowNonTtys)
155 {
156     printf("\nPress any keys -- Ctrl-D exits\n\n");
157     const SavedTermiosMode saved =
158         setRawTerminalMode(allowNonTtys, false, false);
159     char buf[128];
160     while (true) {
161         const ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
162         if (len <= 0) {
163             break;
164         }
165         for (int i = 0; i < len; ++i) {
166             char ctrl = decodeUnixCtrlChar(buf[i]);
167             if (ctrl == '\0') {
168                 putchar(buf[i]);
169             } else {
170                 putchar('^');
171                 putchar(ctrl);
172             }
173         }
174         for (int i = 0; i < len; ++i) {
175             unsigned char uch = buf[i];
176             printf("\t%3d %04o 0x%02x\n", uch, uch, uch);
177             fflush(stdout);
178         }
179         if (buf[0] == 4) {
180             // Ctrl-D
181             break;
182         }
183     }
184     restoreTerminalMode(saved);
185 }
186
187 static void terminalResized(int signo)
188 {
189     mainWakeup().set();
190 }
191
192 static void registerResizeSignalHandler()
193 {
194     struct sigaction resizeSigAct;
195     memset(&resizeSigAct, 0, sizeof(resizeSigAct));
196     resizeSigAct.sa_handler = terminalResized;
197     resizeSigAct.sa_flags = SA_RESTART;
198     sigaction(SIGWINCH, &resizeSigAct, NULL);
199 }
200
201 // Convert the path to a Win32 path if it is a POSIX path, and convert slashes
202 // to backslashes.
203 static std::string convertPosixPathToWin(const std::string &path)
204 {
205     char *tmp;
206 #if defined(CYGWIN_VERSION_CYGWIN_CONV) && \
207         CYGWIN_VERSION_API_MINOR >= CYGWIN_VERSION_CYGWIN_CONV
208     // MSYS2 and versions of Cygwin released after 2009 or so use this API.
209     // The original MSYS still lacks this API.
210     ssize_t newSize = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE,
211                                        path.c_str(), NULL, 0);
212     assert(newSize >= 0);
213     tmp = new char[newSize + 1];
214     ssize_t success = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE,
215                                        path.c_str(), tmp, newSize + 1);
216     assert(success == 0);
217 #else
218     // In the current Cygwin header file, this API is documented as deprecated
219     // because it's restricted to paths of MAX_PATH length.  In the CVS version
220     // of MSYS, the newer API doesn't exist, and this older API is implemented
221     // using msys_p2w, which seems like it would handle paths larger than
222     // MAX_PATH, but there's no way to query how large the new path is.
223     // Hopefully, this is large enough.
224     tmp = new char[MAX_PATH + path.size()];
225     cygwin_conv_to_win32_path(path.c_str(), tmp);
226 #endif
227     for (int i = 0; tmp[i] != '\0'; ++i) {
228         if (tmp[i] == '/')
229             tmp[i] = '\\';
230     }
231     std::string ret(tmp);
232     delete [] tmp;
233     return ret;
234 }
235
236 static std::string resolvePath(const std::string &path)
237 {
238     char ret[PATH_MAX];
239     ret[0] = '\0';
240     if (realpath(path.c_str(), ret) != ret) {
241         return std::string();
242     }
243     return ret;
244 }
245
246 template <size_t N>
247 static bool endsWith(const std::string &path, const char (&suf)[N])
248 {
249     const size_t suffixLen = N - 1;
250     char actualSuf[N];
251     if (path.size() < suffixLen) {
252         return false;
253     }
254     strcpy(actualSuf, &path.c_str()[path.size() - suffixLen]);
255     for (size_t i = 0; i < suffixLen; ++i) {
256         actualSuf[i] = tolower(actualSuf[i]);
257     }
258     return !strcmp(actualSuf, suf);
259 }
260
261 static std::string findProgram(
262     const char *winptyProgName,
263     const std::string &prog)
264 {
265     std::string candidate;
266     if (prog.find('/') == std::string::npos &&
267             prog.find('\\') == std::string::npos) {
268         // XXX: It would be nice to use a lambda here (once/if old MSYS support
269         // is dropped).
270         // Search the PATH.
271         const char *const pathVar = getenv("PATH");
272         const std::string pathList(pathVar ? pathVar : "");
273         size_t elpos = 0;
274         while (true) {
275             const size_t elend = pathList.find(':', elpos);
276             candidate = pathList.substr(elpos, elend - elpos);
277             if (!candidate.empty() && *(candidate.end() - 1) != '/') {
278                 candidate += '/';
279             }
280             candidate += prog;
281             candidate = resolvePath(candidate);
282             if (!candidate.empty()) {
283                 int perm = X_OK;
284                 if (endsWith(candidate, ".bat") || endsWith(candidate, ".cmd")) {
285 #ifdef __MSYS__
286                     // In MSYS/MSYS2, batch files don't have the execute bit
287                     // set, so just check that they're readable.
288                     perm = R_OK;
289 #endif
290                 } else if (endsWith(candidate, ".com") || endsWith(candidate, ".exe")) {
291                     // Do nothing.
292                 } else {
293                     // Make the exe extension explicit so that we don't try to
294                     // run shell scripts with CreateProcess/winpty_spawn.
295                     candidate += ".exe";
296                 }
297                 if (!access(candidate.c_str(), perm)) {
298                     break;
299                 }
300             }
301             if (elend == std::string::npos) {
302                 fprintf(stderr, "%s: error: cannot start '%s': Not found in PATH\n",
303                     winptyProgName, prog.c_str());
304                 exit(1);
305             } else {
306                 elpos = elend + 1;
307             }
308         }
309     } else {
310         candidate = resolvePath(prog);
311         if (candidate.empty()) {
312             std::string errstr(strerror(errno));
313             fprintf(stderr, "%s: error: cannot start '%s': %s\n",
314                 winptyProgName, prog.c_str(), errstr.c_str());
315             exit(1);
316         }
317     }
318     return convertPosixPathToWin(candidate);
319 }
320
321 // Convert argc/argv into a Win32 command-line following the escaping convention
322 // documented on MSDN.  (e.g. see CommandLineToArgvW documentation)
323 static std::string argvToCommandLine(const std::vector<std::string> &argv)
324 {
325     std::string result;
326     for (size_t argIndex = 0; argIndex < argv.size(); ++argIndex) {
327         if (argIndex > 0)
328             result.push_back(' ');
329         const char *arg = argv[argIndex].c_str();
330         const bool quote =
331             strchr(arg, ' ') != NULL ||
332             strchr(arg, '\t') != NULL ||
333             *arg == '\0';
334         if (quote)
335             result.push_back('\"');
336         int bsCount = 0;
337         for (const char *p = arg; *p != '\0'; ++p) {
338             if (*p == '\\') {
339                 bsCount++;
340             } else if (*p == '\"') {
341                 result.append(bsCount * 2 + 1, '\\');
342                 result.push_back('\"');
343                 bsCount = 0;
344             } else {
345                 result.append(bsCount, '\\');
346                 bsCount = 0;
347                 result.push_back(*p);
348             }
349         }
350         if (quote) {
351             result.append(bsCount * 2, '\\');
352             result.push_back('\"');
353         } else {
354             result.append(bsCount, '\\');
355         }
356     }
357     return result;
358 }
359
360 static wchar_t *heapMbsToWcs(const char *text)
361 {
362     // Calling mbstowcs with a NULL first argument seems to be broken on MSYS.
363     // Instead of returning the size of the converted string, it returns 0.
364     // Using strlen(text) * 2 is probably big enough.
365     size_t maxLen = strlen(text) * 2 + 1;
366     wchar_t *ret = new wchar_t[maxLen];
367     size_t len = mbstowcs(ret, text, maxLen);
368     assert(len != (size_t)-1 && len < maxLen);
369     return ret;
370 }
371
372 static char *heapWcsToMbs(const wchar_t *text)
373 {
374     // Calling wcstombs with a NULL first argument seems to be broken on MSYS.
375     // Instead of returning the size of the converted string, it returns 0.
376     // Using wcslen(text) * 3 is big enough for UTF-8 and probably other
377     // encodings.  For UTF-8, codepoints that fit in a single wchar
378     // (U+0000 to U+FFFF) are encoded using 1-3 bytes.  The remaining code
379     // points needs two wchar's and are encoded using 4 bytes.
380     size_t maxLen = wcslen(text) * 3 + 1;
381     char *ret = new char[maxLen];
382     size_t len = wcstombs(ret, text, maxLen);
383     if (len == (size_t)-1 || len >= maxLen) {
384         delete [] ret;
385         return NULL;
386     } else {
387         return ret;
388     }
389 }
390
391 static std::string wcsToMbs(const wchar_t *text)
392 {
393     std::string ret;
394     const char *ptr = heapWcsToMbs(text);
395     if (ptr != NULL) {
396         ret = ptr;
397         delete [] ptr;
398     }
399     return ret;
400 }
401
402 void setupWin32Environment()
403 {
404     std::map<std::string, std::string> varsToCopy;
405     const char *vars[] = {
406         "WINPTY_DEBUG",
407         "WINPTY_SHOW_CONSOLE",
408         NULL
409     };
410     for (int i = 0; vars[i] != NULL; ++i) {
411         const char *cstr = getenv(vars[i]);
412         if (cstr != NULL && cstr[0] != '\0') {
413             varsToCopy[vars[i]] = cstr;
414         }
415     }
416
417 #if defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 48 || \
418         !defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 153
419     // Use CW_SYNC_WINENV to copy the Unix environment to the Win32
420     // environment.  The command performs special translation on some variables
421     // (such as PATH and TMP).  It also copies the debugging environment
422     // variables.
423     //
424     // Note that the API minor versions have diverged in Cygwin and MSYS.
425     // CW_SYNC_WINENV was added to Cygwin in version 153.  (Cygwin's
426     // include/cygwin/version.h says that CW_SETUP_WINENV was added in 153.
427     // The flag was renamed 8 days after it was added, but the API docs weren't
428     // updated.)  The flag was added to MSYS in version 48.
429     //
430     // Also, in my limited testing, this call seems to be necessary with Cygwin
431     // but unnecessary with MSYS.  Perhaps MSYS is automatically syncing the
432     // Unix environment with the Win32 environment before starting console.exe?
433     // It shouldn't hurt to call it for MSYS.
434     cygwin_internal(CW_SYNC_WINENV);
435 #endif
436
437     // Copy debugging environment variables from the Cygwin environment
438     // to the Win32 environment so the agent will inherit it.
439     for (std::map<std::string, std::string>::iterator it = varsToCopy.begin();
440             it != varsToCopy.end();
441             ++it) {
442         wchar_t *nameW = heapMbsToWcs(it->first.c_str());
443         wchar_t *valueW = heapMbsToWcs(it->second.c_str());
444         SetEnvironmentVariableW(nameW, valueW);
445         delete [] nameW;
446         delete [] valueW;
447     }
448
449     // Clear the TERM variable.  The child process's immediate console/terminal
450     // environment is a Windows console, not the terminal that winpty is
451     // communicating with.  Leaving the TERM variable set can break programs in
452     // various ways.  (e.g. arrows keys broken in Cygwin less, IronPython's
453     // help(...) function doesn't start, misc programs decide they should
454     // output color escape codes on pre-Win10).  See
455     // https://github.com/rprichard/winpty/issues/43.
456     SetEnvironmentVariableW(L"TERM", NULL);
457 }
458
459 static void usage(const char *program, int exitCode)
460 {
461     printf("Usage: %s [options] [--] program [args]\n", program);
462     printf("\n");
463     printf("Options:\n");
464     printf("  -h, --help  Show this help message\n");
465     printf("  --mouse     Enable terminal mouse input\n");
466     printf("  --showkey   Dump STDIN escape sequences\n");
467     printf("  --version   Show the winpty version number\n");
468     exit(exitCode);
469 }
470
471 struct Arguments {
472     std::vector<std::string> childArgv;
473     bool mouseInput;
474     bool testAllowNonTtys;
475     bool testConerr;
476     bool testPlainOutput;
477     bool testColorEscapes;
478 };
479
480 static void parseArguments(int argc, char *argv[], Arguments &out)
481 {
482     out.mouseInput = false;
483     out.testAllowNonTtys = false;
484     out.testConerr = false;
485     out.testPlainOutput = false;
486     out.testColorEscapes = false;
487     bool doShowKeys = false;
488     const char *const program = argc >= 1 ? argv[0] : "<program>";
489     int argi = 1;
490     while (argi < argc) {
491         std::string arg(argv[argi++]);
492         if (arg.size() >= 1 && arg[0] == '-') {
493             if (arg == "-h" || arg == "--help") {
494                 usage(program, 0);
495             } else if (arg == "--mouse") {
496                 out.mouseInput = true;
497             } else if (arg == "--showkey") {
498                 doShowKeys = true;
499             } else if (arg == "--version") {
500                 dumpVersionToStdout();
501                 exit(0);
502             } else if (arg == "-Xallow-non-tty") {
503                 out.testAllowNonTtys = true;
504             } else if (arg == "-Xconerr") {
505                 out.testConerr = true;
506             } else if (arg == "-Xplain") {
507                 out.testPlainOutput = true;
508             } else if (arg == "-Xcolor") {
509                 out.testColorEscapes = true;
510             } else if (arg == "--") {
511                 break;
512             } else {
513                 fprintf(stderr, "Error: unrecognized option: '%s'\n",
514                     arg.c_str());
515                 exit(1);
516             }
517         } else {
518             out.childArgv.push_back(arg);
519             break;
520         }
521     }
522     for (; argi < argc; ++argi) {
523         out.childArgv.push_back(argv[argi]);
524     }
525     if (doShowKeys) {
526         debugShowKey(out.testAllowNonTtys);
527         exit(0);
528     }
529     if (out.childArgv.size() == 0) {
530         usage(program, 1);
531     }
532 }
533
534 static std::string errorMessageToString(DWORD err)
535 {
536     // Use FormatMessageW rather than FormatMessageA, because we want to use
537     // wcstombs to convert to the Cygwin locale, which might not match the
538     // codepage FormatMessageA would use.  We need to convert using wcstombs,
539     // rather than print using %ls, because %ls doesn't work in the original
540     // MSYS.
541     wchar_t *wideMsgPtr = NULL;
542     const DWORD formatRet = FormatMessageW(
543         FORMAT_MESSAGE_FROM_SYSTEM |
544             FORMAT_MESSAGE_ALLOCATE_BUFFER |
545             FORMAT_MESSAGE_IGNORE_INSERTS,
546         NULL,
547         err,
548         MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
549         reinterpret_cast<wchar_t*>(&wideMsgPtr),
550         0,
551         NULL);
552     if (formatRet == 0 || wideMsgPtr == NULL) {
553         return std::string();
554     }
555     std::string msg = wcsToMbs(wideMsgPtr);
556     LocalFree(wideMsgPtr);
557     const size_t pos = msg.find_last_not_of(" \r\n\t");
558     if (pos == std::string::npos) {
559         msg.clear();
560     } else {
561         msg.erase(pos + 1);
562     }
563     return msg;
564 }
565
566 static std::string formatErrorMessage(DWORD err)
567 {
568     char buf[64];
569     sprintf(buf, "error %#x", static_cast<unsigned int>(err));
570     std::string ret = errorMessageToString(err);
571     if (ret.empty()) {
572         ret += buf;
573     } else {
574         ret += " (";
575         ret += buf;
576         ret += ")";
577     }
578     return ret;
579 }
580
581 int main(int argc, char *argv[])
582 {
583     setlocale(LC_ALL, "");
584
585     g_mainWakeup = new WakeupFd();
586
587     Arguments args;
588     parseArguments(argc, argv, args);
589
590     setupWin32Environment();
591
592     winsize sz = { 0 };
593     sz.ws_col = 80;
594     sz.ws_row = 25;
595     ioctl(STDIN_FILENO, TIOCGWINSZ, &sz);
596
597     DWORD agentFlags = WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION;
598     if (args.testConerr)        { agentFlags |= WINPTY_FLAG_CONERR; }
599     if (args.testPlainOutput)   { agentFlags |= WINPTY_FLAG_PLAIN_OUTPUT; }
600     if (args.testColorEscapes)  { agentFlags |= WINPTY_FLAG_COLOR_ESCAPES; }
601     winpty_config_t *agentCfg = winpty_config_new(agentFlags, NULL);
602     assert(agentCfg != NULL);
603     winpty_config_set_initial_size(agentCfg, sz.ws_col, sz.ws_row);
604     if (args.mouseInput) {
605         winpty_config_set_mouse_mode(agentCfg, WINPTY_MOUSE_MODE_FORCE);
606     }
607
608     winpty_error_ptr_t openErr = NULL;
609     winpty_t *wp = winpty_open(agentCfg, &openErr);
610     if (wp == NULL) {
611         fprintf(stderr, "Error creating winpty: %s\n",
612             wcsToMbs(winpty_error_msg(openErr)).c_str());
613         exit(1);
614     }
615     winpty_config_free(agentCfg);
616     winpty_error_free(openErr);
617
618     HANDLE conin = CreateFileW(winpty_conin_name(wp), GENERIC_WRITE, 0, NULL,
619                                OPEN_EXISTING, 0, NULL);
620     HANDLE conout = CreateFileW(winpty_conout_name(wp), GENERIC_READ, 0, NULL,
621                                 OPEN_EXISTING, 0, NULL);
622     assert(conin != INVALID_HANDLE_VALUE);
623     assert(conout != INVALID_HANDLE_VALUE);
624     HANDLE conerr = NULL;
625     if (args.testConerr) {
626         conerr = CreateFileW(winpty_conerr_name(wp), GENERIC_READ, 0, NULL,
627                              OPEN_EXISTING, 0, NULL);
628         assert(conerr != INVALID_HANDLE_VALUE);
629     }
630
631     HANDLE childHandle = NULL;
632
633     {
634         // Start the child process under the console.
635         args.childArgv[0] = findProgram(argv[0], args.childArgv[0]);
636         std::string cmdLine = argvToCommandLine(args.childArgv);
637         wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str());
638
639         winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new(
640                 WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN,
641                 NULL, cmdLineW, NULL, NULL, NULL);
642         assert(spawnCfg != NULL);
643
644         winpty_error_ptr_t spawnErr = NULL;
645         DWORD lastError = 0;
646         BOOL spawnRet = winpty_spawn(wp, spawnCfg, &childHandle, NULL,
647             &lastError, &spawnErr);
648         winpty_spawn_config_free(spawnCfg);
649
650         if (!spawnRet) {
651             winpty_result_t spawnCode = winpty_error_code(spawnErr);
652             if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) {
653                 fprintf(stderr, "%s: error: cannot start '%s': %s\n",
654                     argv[0],
655                     cmdLine.c_str(),
656                     formatErrorMessage(lastError).c_str());
657             } else {
658                 fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n",
659                     argv[0],
660                     cmdLine.c_str(),
661                     wcsToMbs(winpty_error_msg(spawnErr)).c_str());
662             }
663             exit(1);
664         }
665         winpty_error_free(spawnErr);
666         delete [] cmdLineW;
667     }
668
669     registerResizeSignalHandler();
670     SavedTermiosMode mode =
671         setRawTerminalMode(args.testAllowNonTtys, true, args.testConerr);
672
673     InputHandler inputHandler(conin, STDIN_FILENO, mainWakeup());
674     OutputHandler outputHandler(conout, STDOUT_FILENO, mainWakeup());
675     OutputHandler *errorHandler = NULL;
676     if (args.testConerr) {
677         errorHandler = new OutputHandler(conerr, STDERR_FILENO, mainWakeup());
678     }
679
680     while (true) {
681         fd_set readfds;
682         FD_ZERO(&readfds);
683         FD_SET(mainWakeup().fd(), &readfds);
684         selectWrapper("main thread", mainWakeup().fd() + 1, &readfds);
685         mainWakeup().reset();
686
687         // Check for terminal resize.
688         {
689             winsize sz2;
690             ioctl(STDIN_FILENO, TIOCGWINSZ, &sz2);
691             if (memcmp(&sz, &sz2, sizeof(sz)) != 0) {
692                 sz = sz2;
693                 winpty_set_size(wp, sz.ws_col, sz.ws_row, NULL);
694             }
695         }
696
697         // Check for an I/O handler shutting down (possibly indicating that the
698         // child process has exited).
699         if (inputHandler.isComplete() || outputHandler.isComplete() ||
700                 (errorHandler != NULL && errorHandler->isComplete())) {
701             break;
702         }
703     }
704
705     // Kill the agent connection.  This will kill the agent, closing the CONIN
706     // and CONOUT pipes on the agent pipe, prompting our I/O handler to shut
707     // down.
708     winpty_free(wp);
709
710     inputHandler.shutdown();
711     outputHandler.shutdown();
712     CloseHandle(conin);
713     CloseHandle(conout);
714
715     if (errorHandler != NULL) {
716         errorHandler->shutdown();
717         delete errorHandler;
718         CloseHandle(conerr);
719     }
720
721     restoreTerminalMode(mode);
722
723     DWORD exitCode = 0;
724     if (!GetExitCodeProcess(childHandle, &exitCode)) {
725         exitCode = 1;
726     }
727     CloseHandle(childHandle);
728     return exitCode;
729 }