1 // Copyright (c) 2011-2015 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
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.
26 #include <cygwin/version.h>
32 #include <sys/ioctl.h>
33 #include <sys/select.h>
34 #include <sys/cygwin.h>
44 #include "../shared/DebugClient.h"
45 #include "../shared/UnixCtrlChars.h"
46 #include "../shared/WinptyVersion.h"
47 #include "InputHandler.h"
48 #include "OutputHandler.h"
54 static WakeupFd *g_mainWakeup = NULL;
56 static WakeupFd &mainWakeup()
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);
66 struct SavedTermiosMode {
72 // Put the input terminal into non-canonical mode.
73 static SavedTermiosMode setRawTerminalMode(
74 bool allowNonTtys, bool setStdout, bool setStderr)
77 const char *const kNames[3] = { "stdin", "stdout", "stderr" };
80 ret.valid[1] = setStdout;
81 ret.valid[2] = setStderr;
83 for (int i = 0; i < 3; ++i) {
90 fprintf(stderr, "%s is not a tty\n", kNames[i]);
95 if (tcgetattr(i, &ret.mode[i]) < 0) {
96 perror("tcgetattr failed");
102 if (ret.valid[STDIN_FILENO]) {
104 if (tcgetattr(STDIN_FILENO, &buf) < 0) {
105 perror("tcgetattr failed");
108 buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
109 buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
110 buf.c_cflag &= ~(CSIZE | PARENB);
112 buf.c_cc[VMIN] = 1; // blocking read
114 if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) {
115 fprintf(stderr, "tcsetattr failed\n");
120 for (int i = STDOUT_FILENO; i <= STDERR_FILENO; ++i) {
125 if (tcgetattr(i, &buf) < 0) {
126 perror("tcgetattr failed");
129 buf.c_cflag &= ~(CSIZE | PARENB);
131 buf.c_oflag &= ~OPOST;
132 if (tcsetattr(i, TCSAFLUSH, &buf) < 0) {
133 fprintf(stderr, "tcsetattr failed\n");
141 static void restoreTerminalMode(const SavedTermiosMode &original)
143 for (int i = 0; i < 3; ++i) {
144 if (!original.valid[i]) {
147 if (tcsetattr(i, TCSAFLUSH, &original.mode[i]) < 0) {
148 perror("error restoring terminal mode");
154 static void debugShowKey(bool allowNonTtys)
156 printf("\nPress any keys -- Ctrl-D exits\n\n");
157 const SavedTermiosMode saved =
158 setRawTerminalMode(allowNonTtys, false, false);
161 const ssize_t len = read(STDIN_FILENO, buf, sizeof(buf));
165 for (int i = 0; i < len; ++i) {
166 char ctrl = decodeUnixCtrlChar(buf[i]);
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);
184 restoreTerminalMode(saved);
187 static void terminalResized(int signo)
192 static void registerResizeSignalHandler()
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);
201 // Convert the path to a Win32 path if it is a POSIX path, and convert slashes
203 static std::string convertPosixPathToWin(const std::string &path)
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);
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);
227 for (int i = 0; tmp[i] != '\0'; ++i) {
231 std::string ret(tmp);
236 static std::string resolvePath(const std::string &path)
240 if (realpath(path.c_str(), ret) != ret) {
241 return std::string();
247 static bool endsWith(const std::string &path, const char (&suf)[N])
249 const size_t suffixLen = N - 1;
251 if (path.size() < suffixLen) {
254 strcpy(actualSuf, &path.c_str()[path.size() - suffixLen]);
255 for (size_t i = 0; i < suffixLen; ++i) {
256 actualSuf[i] = tolower(actualSuf[i]);
258 return !strcmp(actualSuf, suf);
261 static std::string findProgram(
262 const char *winptyProgName,
263 const std::string &prog)
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
271 const char *const pathVar = getenv("PATH");
272 const std::string pathList(pathVar ? pathVar : "");
275 const size_t elend = pathList.find(':', elpos);
276 candidate = pathList.substr(elpos, elend - elpos);
277 if (!candidate.empty() && *(candidate.end() - 1) != '/') {
281 candidate = resolvePath(candidate);
282 if (!candidate.empty()) {
284 if (endsWith(candidate, ".bat") || endsWith(candidate, ".cmd")) {
286 // In MSYS/MSYS2, batch files don't have the execute bit
287 // set, so just check that they're readable.
290 } else if (endsWith(candidate, ".com") || endsWith(candidate, ".exe")) {
293 // Make the exe extension explicit so that we don't try to
294 // run shell scripts with CreateProcess/winpty_spawn.
297 if (!access(candidate.c_str(), perm)) {
301 if (elend == std::string::npos) {
302 fprintf(stderr, "%s: error: cannot start '%s': Not found in PATH\n",
303 winptyProgName, prog.c_str());
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());
318 return convertPosixPathToWin(candidate);
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)
326 for (size_t argIndex = 0; argIndex < argv.size(); ++argIndex) {
328 result.push_back(' ');
329 const char *arg = argv[argIndex].c_str();
331 strchr(arg, ' ') != NULL ||
332 strchr(arg, '\t') != NULL ||
335 result.push_back('\"');
337 for (const char *p = arg; *p != '\0'; ++p) {
340 } else if (*p == '\"') {
341 result.append(bsCount * 2 + 1, '\\');
342 result.push_back('\"');
345 result.append(bsCount, '\\');
347 result.push_back(*p);
351 result.append(bsCount * 2, '\\');
352 result.push_back('\"');
354 result.append(bsCount, '\\');
360 static wchar_t *heapMbsToWcs(const char *text)
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);
372 static char *heapWcsToMbs(const wchar_t *text)
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) {
391 static std::string wcsToMbs(const wchar_t *text)
394 const char *ptr = heapWcsToMbs(text);
402 void setupWin32Environment()
404 std::map<std::string, std::string> varsToCopy;
405 const char *vars[] = {
407 "WINPTY_SHOW_CONSOLE",
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;
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
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.
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);
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();
442 wchar_t *nameW = heapMbsToWcs(it->first.c_str());
443 wchar_t *valueW = heapMbsToWcs(it->second.c_str());
444 SetEnvironmentVariableW(nameW, valueW);
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);
459 static void usage(const char *program, int exitCode)
461 printf("Usage: %s [options] [--] program [args]\n", program);
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");
472 std::vector<std::string> childArgv;
474 bool testAllowNonTtys;
476 bool testPlainOutput;
477 bool testColorEscapes;
480 static void parseArguments(int argc, char *argv[], Arguments &out)
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>";
490 while (argi < argc) {
491 std::string arg(argv[argi++]);
492 if (arg.size() >= 1 && arg[0] == '-') {
493 if (arg == "-h" || arg == "--help") {
495 } else if (arg == "--mouse") {
496 out.mouseInput = true;
497 } else if (arg == "--showkey") {
499 } else if (arg == "--version") {
500 dumpVersionToStdout();
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 == "--") {
513 fprintf(stderr, "Error: unrecognized option: '%s'\n",
518 out.childArgv.push_back(arg);
522 for (; argi < argc; ++argi) {
523 out.childArgv.push_back(argv[argi]);
526 debugShowKey(out.testAllowNonTtys);
529 if (out.childArgv.size() == 0) {
534 static std::string errorMessageToString(DWORD err)
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
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,
548 MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
549 reinterpret_cast<wchar_t*>(&wideMsgPtr),
552 if (formatRet == 0 || wideMsgPtr == NULL) {
553 return std::string();
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) {
566 static std::string formatErrorMessage(DWORD err)
569 sprintf(buf, "error %#x", static_cast<unsigned int>(err));
570 std::string ret = errorMessageToString(err);
581 int main(int argc, char *argv[])
583 setlocale(LC_ALL, "");
585 g_mainWakeup = new WakeupFd();
588 parseArguments(argc, argv, args);
590 setupWin32Environment();
595 ioctl(STDIN_FILENO, TIOCGWINSZ, &sz);
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);
608 winpty_error_ptr_t openErr = NULL;
609 winpty_t *wp = winpty_open(agentCfg, &openErr);
611 fprintf(stderr, "Error creating winpty: %s\n",
612 wcsToMbs(winpty_error_msg(openErr)).c_str());
615 winpty_config_free(agentCfg);
616 winpty_error_free(openErr);
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);
631 HANDLE childHandle = NULL;
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());
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);
644 winpty_error_ptr_t spawnErr = NULL;
646 BOOL spawnRet = winpty_spawn(wp, spawnCfg, &childHandle, NULL,
647 &lastError, &spawnErr);
648 winpty_spawn_config_free(spawnCfg);
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",
656 formatErrorMessage(lastError).c_str());
658 fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n",
661 wcsToMbs(winpty_error_msg(spawnErr)).c_str());
665 winpty_error_free(spawnErr);
669 registerResizeSignalHandler();
670 SavedTermiosMode mode =
671 setRawTerminalMode(args.testAllowNonTtys, true, args.testConerr);
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());
683 FD_SET(mainWakeup().fd(), &readfds);
684 selectWrapper("main thread", mainWakeup().fd() + 1, &readfds);
685 mainWakeup().reset();
687 // Check for terminal resize.
690 ioctl(STDIN_FILENO, TIOCGWINSZ, &sz2);
691 if (memcmp(&sz, &sz2, sizeof(sz)) != 0) {
693 winpty_set_size(wp, sz.ws_col, sz.ws_row, NULL);
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())) {
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
710 inputHandler.shutdown();
711 outputHandler.shutdown();
715 if (errorHandler != NULL) {
716 errorHandler->shutdown();
721 restoreTerminalMode(mode);
724 if (!GetExitCodeProcess(childHandle, &exitCode)) {
727 CloseHandle(childHandle);