X-Git-Url: https://git.josue.xyz/?p=VSoRC%2F.git;a=blobdiff_plain;f=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Funix-adapter%2Fmain.cc;fp=node_modules%2Fnode-pty%2Fdeps%2Fwinpty%2Fsrc%2Funix-adapter%2Fmain.cc;h=992cb70e449b778b0044024c120f86611392f603;hp=0000000000000000000000000000000000000000;hb=e79e4a5a87f3e84f7c1777f10a954453a69bf540;hpb=4339da12467b75fb8b6ca831f4bf0081c485ed2c diff --git a/node_modules/node-pty/deps/winpty/src/unix-adapter/main.cc b/node_modules/node-pty/deps/winpty/src/unix-adapter/main.cc new file mode 100644 index 0000000..992cb70 --- /dev/null +++ b/node_modules/node-pty/deps/winpty/src/unix-adapter/main.cc @@ -0,0 +1,729 @@ +// Copyright (c) 2011-2015 Ryan Prichard +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to +// deal in the Software without restriction, including without limitation the +// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +// sell copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +// IN THE SOFTWARE. + +// MSYS's sys/cygwin.h header only declares cygwin_internal if WINVER is +// defined, which is defined in windows.h. Therefore, include windows.h early. +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "../shared/DebugClient.h" +#include "../shared/UnixCtrlChars.h" +#include "../shared/WinptyVersion.h" +#include "InputHandler.h" +#include "OutputHandler.h" +#include "Util.h" +#include "WakeupFd.h" + +#define CSI "\x1b[" + +static WakeupFd *g_mainWakeup = NULL; + +static WakeupFd &mainWakeup() +{ + if (g_mainWakeup == NULL) { + static const char msg[] = "Internal error: g_mainWakeup is NULL\r\n"; + write(STDERR_FILENO, msg, sizeof(msg) - 1); + abort(); + } + return *g_mainWakeup; +} + +struct SavedTermiosMode { + int count; + bool valid[3]; + termios mode[3]; +}; + +// Put the input terminal into non-canonical mode. +static SavedTermiosMode setRawTerminalMode( + bool allowNonTtys, bool setStdout, bool setStderr) +{ + SavedTermiosMode ret; + const char *const kNames[3] = { "stdin", "stdout", "stderr" }; + + ret.valid[0] = true; + ret.valid[1] = setStdout; + ret.valid[2] = setStderr; + + for (int i = 0; i < 3; ++i) { + if (!ret.valid[i]) { + continue; + } + if (!isatty(i)) { + ret.valid[i] = false; + if (!allowNonTtys) { + fprintf(stderr, "%s is not a tty\n", kNames[i]); + exit(1); + } + } else { + ret.valid[i] = true; + if (tcgetattr(i, &ret.mode[i]) < 0) { + perror("tcgetattr failed"); + exit(1); + } + } + } + + if (ret.valid[STDIN_FILENO]) { + termios buf; + if (tcgetattr(STDIN_FILENO, &buf) < 0) { + perror("tcgetattr failed"); + exit(1); + } + buf.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG); + buf.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON); + buf.c_cflag &= ~(CSIZE | PARENB); + buf.c_cflag |= CS8; + buf.c_cc[VMIN] = 1; // blocking read + buf.c_cc[VTIME] = 0; + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &buf) < 0) { + fprintf(stderr, "tcsetattr failed\n"); + exit(1); + } + } + + for (int i = STDOUT_FILENO; i <= STDERR_FILENO; ++i) { + if (!ret.valid[i]) { + continue; + } + termios buf; + if (tcgetattr(i, &buf) < 0) { + perror("tcgetattr failed"); + exit(1); + } + buf.c_cflag &= ~(CSIZE | PARENB); + buf.c_cflag |= CS8; + buf.c_oflag &= ~OPOST; + if (tcsetattr(i, TCSAFLUSH, &buf) < 0) { + fprintf(stderr, "tcsetattr failed\n"); + exit(1); + } + } + + return ret; +} + +static void restoreTerminalMode(const SavedTermiosMode &original) +{ + for (int i = 0; i < 3; ++i) { + if (!original.valid[i]) { + continue; + } + if (tcsetattr(i, TCSAFLUSH, &original.mode[i]) < 0) { + perror("error restoring terminal mode"); + exit(1); + } + } +} + +static void debugShowKey(bool allowNonTtys) +{ + printf("\nPress any keys -- Ctrl-D exits\n\n"); + const SavedTermiosMode saved = + setRawTerminalMode(allowNonTtys, false, false); + char buf[128]; + while (true) { + const ssize_t len = read(STDIN_FILENO, buf, sizeof(buf)); + if (len <= 0) { + break; + } + for (int i = 0; i < len; ++i) { + char ctrl = decodeUnixCtrlChar(buf[i]); + if (ctrl == '\0') { + putchar(buf[i]); + } else { + putchar('^'); + putchar(ctrl); + } + } + for (int i = 0; i < len; ++i) { + unsigned char uch = buf[i]; + printf("\t%3d %04o 0x%02x\n", uch, uch, uch); + fflush(stdout); + } + if (buf[0] == 4) { + // Ctrl-D + break; + } + } + restoreTerminalMode(saved); +} + +static void terminalResized(int signo) +{ + mainWakeup().set(); +} + +static void registerResizeSignalHandler() +{ + struct sigaction resizeSigAct; + memset(&resizeSigAct, 0, sizeof(resizeSigAct)); + resizeSigAct.sa_handler = terminalResized; + resizeSigAct.sa_flags = SA_RESTART; + sigaction(SIGWINCH, &resizeSigAct, NULL); +} + +// Convert the path to a Win32 path if it is a POSIX path, and convert slashes +// to backslashes. +static std::string convertPosixPathToWin(const std::string &path) +{ + char *tmp; +#if defined(CYGWIN_VERSION_CYGWIN_CONV) && \ + CYGWIN_VERSION_API_MINOR >= CYGWIN_VERSION_CYGWIN_CONV + // MSYS2 and versions of Cygwin released after 2009 or so use this API. + // The original MSYS still lacks this API. + ssize_t newSize = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, + path.c_str(), NULL, 0); + assert(newSize >= 0); + tmp = new char[newSize + 1]; + ssize_t success = cygwin_conv_path(CCP_POSIX_TO_WIN_A | CCP_ABSOLUTE, + path.c_str(), tmp, newSize + 1); + assert(success == 0); +#else + // In the current Cygwin header file, this API is documented as deprecated + // because it's restricted to paths of MAX_PATH length. In the CVS version + // of MSYS, the newer API doesn't exist, and this older API is implemented + // using msys_p2w, which seems like it would handle paths larger than + // MAX_PATH, but there's no way to query how large the new path is. + // Hopefully, this is large enough. + tmp = new char[MAX_PATH + path.size()]; + cygwin_conv_to_win32_path(path.c_str(), tmp); +#endif + for (int i = 0; tmp[i] != '\0'; ++i) { + if (tmp[i] == '/') + tmp[i] = '\\'; + } + std::string ret(tmp); + delete [] tmp; + return ret; +} + +static std::string resolvePath(const std::string &path) +{ + char ret[PATH_MAX]; + ret[0] = '\0'; + if (realpath(path.c_str(), ret) != ret) { + return std::string(); + } + return ret; +} + +template +static bool endsWith(const std::string &path, const char (&suf)[N]) +{ + const size_t suffixLen = N - 1; + char actualSuf[N]; + if (path.size() < suffixLen) { + return false; + } + strcpy(actualSuf, &path.c_str()[path.size() - suffixLen]); + for (size_t i = 0; i < suffixLen; ++i) { + actualSuf[i] = tolower(actualSuf[i]); + } + return !strcmp(actualSuf, suf); +} + +static std::string findProgram( + const char *winptyProgName, + const std::string &prog) +{ + std::string candidate; + if (prog.find('/') == std::string::npos && + prog.find('\\') == std::string::npos) { + // XXX: It would be nice to use a lambda here (once/if old MSYS support + // is dropped). + // Search the PATH. + const char *const pathVar = getenv("PATH"); + const std::string pathList(pathVar ? pathVar : ""); + size_t elpos = 0; + while (true) { + const size_t elend = pathList.find(':', elpos); + candidate = pathList.substr(elpos, elend - elpos); + if (!candidate.empty() && *(candidate.end() - 1) != '/') { + candidate += '/'; + } + candidate += prog; + candidate = resolvePath(candidate); + if (!candidate.empty()) { + int perm = X_OK; + if (endsWith(candidate, ".bat") || endsWith(candidate, ".cmd")) { +#ifdef __MSYS__ + // In MSYS/MSYS2, batch files don't have the execute bit + // set, so just check that they're readable. + perm = R_OK; +#endif + } else if (endsWith(candidate, ".com") || endsWith(candidate, ".exe")) { + // Do nothing. + } else { + // Make the exe extension explicit so that we don't try to + // run shell scripts with CreateProcess/winpty_spawn. + candidate += ".exe"; + } + if (!access(candidate.c_str(), perm)) { + break; + } + } + if (elend == std::string::npos) { + fprintf(stderr, "%s: error: cannot start '%s': Not found in PATH\n", + winptyProgName, prog.c_str()); + exit(1); + } else { + elpos = elend + 1; + } + } + } else { + candidate = resolvePath(prog); + if (candidate.empty()) { + std::string errstr(strerror(errno)); + fprintf(stderr, "%s: error: cannot start '%s': %s\n", + winptyProgName, prog.c_str(), errstr.c_str()); + exit(1); + } + } + return convertPosixPathToWin(candidate); +} + +// Convert argc/argv into a Win32 command-line following the escaping convention +// documented on MSDN. (e.g. see CommandLineToArgvW documentation) +static std::string argvToCommandLine(const std::vector &argv) +{ + std::string result; + for (size_t argIndex = 0; argIndex < argv.size(); ++argIndex) { + if (argIndex > 0) + result.push_back(' '); + const char *arg = argv[argIndex].c_str(); + const bool quote = + strchr(arg, ' ') != NULL || + strchr(arg, '\t') != NULL || + *arg == '\0'; + if (quote) + result.push_back('\"'); + int bsCount = 0; + for (const char *p = arg; *p != '\0'; ++p) { + if (*p == '\\') { + bsCount++; + } else if (*p == '\"') { + result.append(bsCount * 2 + 1, '\\'); + result.push_back('\"'); + bsCount = 0; + } else { + result.append(bsCount, '\\'); + bsCount = 0; + result.push_back(*p); + } + } + if (quote) { + result.append(bsCount * 2, '\\'); + result.push_back('\"'); + } else { + result.append(bsCount, '\\'); + } + } + return result; +} + +static wchar_t *heapMbsToWcs(const char *text) +{ + // Calling mbstowcs with a NULL first argument seems to be broken on MSYS. + // Instead of returning the size of the converted string, it returns 0. + // Using strlen(text) * 2 is probably big enough. + size_t maxLen = strlen(text) * 2 + 1; + wchar_t *ret = new wchar_t[maxLen]; + size_t len = mbstowcs(ret, text, maxLen); + assert(len != (size_t)-1 && len < maxLen); + return ret; +} + +static char *heapWcsToMbs(const wchar_t *text) +{ + // Calling wcstombs with a NULL first argument seems to be broken on MSYS. + // Instead of returning the size of the converted string, it returns 0. + // Using wcslen(text) * 3 is big enough for UTF-8 and probably other + // encodings. For UTF-8, codepoints that fit in a single wchar + // (U+0000 to U+FFFF) are encoded using 1-3 bytes. The remaining code + // points needs two wchar's and are encoded using 4 bytes. + size_t maxLen = wcslen(text) * 3 + 1; + char *ret = new char[maxLen]; + size_t len = wcstombs(ret, text, maxLen); + if (len == (size_t)-1 || len >= maxLen) { + delete [] ret; + return NULL; + } else { + return ret; + } +} + +static std::string wcsToMbs(const wchar_t *text) +{ + std::string ret; + const char *ptr = heapWcsToMbs(text); + if (ptr != NULL) { + ret = ptr; + delete [] ptr; + } + return ret; +} + +void setupWin32Environment() +{ + std::map varsToCopy; + const char *vars[] = { + "WINPTY_DEBUG", + "WINPTY_SHOW_CONSOLE", + NULL + }; + for (int i = 0; vars[i] != NULL; ++i) { + const char *cstr = getenv(vars[i]); + if (cstr != NULL && cstr[0] != '\0') { + varsToCopy[vars[i]] = cstr; + } + } + +#if defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 48 || \ + !defined(__MSYS__) && CYGWIN_VERSION_API_MINOR >= 153 + // Use CW_SYNC_WINENV to copy the Unix environment to the Win32 + // environment. The command performs special translation on some variables + // (such as PATH and TMP). It also copies the debugging environment + // variables. + // + // Note that the API minor versions have diverged in Cygwin and MSYS. + // CW_SYNC_WINENV was added to Cygwin in version 153. (Cygwin's + // include/cygwin/version.h says that CW_SETUP_WINENV was added in 153. + // The flag was renamed 8 days after it was added, but the API docs weren't + // updated.) The flag was added to MSYS in version 48. + // + // Also, in my limited testing, this call seems to be necessary with Cygwin + // but unnecessary with MSYS. Perhaps MSYS is automatically syncing the + // Unix environment with the Win32 environment before starting console.exe? + // It shouldn't hurt to call it for MSYS. + cygwin_internal(CW_SYNC_WINENV); +#endif + + // Copy debugging environment variables from the Cygwin environment + // to the Win32 environment so the agent will inherit it. + for (std::map::iterator it = varsToCopy.begin(); + it != varsToCopy.end(); + ++it) { + wchar_t *nameW = heapMbsToWcs(it->first.c_str()); + wchar_t *valueW = heapMbsToWcs(it->second.c_str()); + SetEnvironmentVariableW(nameW, valueW); + delete [] nameW; + delete [] valueW; + } + + // Clear the TERM variable. The child process's immediate console/terminal + // environment is a Windows console, not the terminal that winpty is + // communicating with. Leaving the TERM variable set can break programs in + // various ways. (e.g. arrows keys broken in Cygwin less, IronPython's + // help(...) function doesn't start, misc programs decide they should + // output color escape codes on pre-Win10). See + // https://github.com/rprichard/winpty/issues/43. + SetEnvironmentVariableW(L"TERM", NULL); +} + +static void usage(const char *program, int exitCode) +{ + printf("Usage: %s [options] [--] program [args]\n", program); + printf("\n"); + printf("Options:\n"); + printf(" -h, --help Show this help message\n"); + printf(" --mouse Enable terminal mouse input\n"); + printf(" --showkey Dump STDIN escape sequences\n"); + printf(" --version Show the winpty version number\n"); + exit(exitCode); +} + +struct Arguments { + std::vector childArgv; + bool mouseInput; + bool testAllowNonTtys; + bool testConerr; + bool testPlainOutput; + bool testColorEscapes; +}; + +static void parseArguments(int argc, char *argv[], Arguments &out) +{ + out.mouseInput = false; + out.testAllowNonTtys = false; + out.testConerr = false; + out.testPlainOutput = false; + out.testColorEscapes = false; + bool doShowKeys = false; + const char *const program = argc >= 1 ? argv[0] : ""; + int argi = 1; + while (argi < argc) { + std::string arg(argv[argi++]); + if (arg.size() >= 1 && arg[0] == '-') { + if (arg == "-h" || arg == "--help") { + usage(program, 0); + } else if (arg == "--mouse") { + out.mouseInput = true; + } else if (arg == "--showkey") { + doShowKeys = true; + } else if (arg == "--version") { + dumpVersionToStdout(); + exit(0); + } else if (arg == "-Xallow-non-tty") { + out.testAllowNonTtys = true; + } else if (arg == "-Xconerr") { + out.testConerr = true; + } else if (arg == "-Xplain") { + out.testPlainOutput = true; + } else if (arg == "-Xcolor") { + out.testColorEscapes = true; + } else if (arg == "--") { + break; + } else { + fprintf(stderr, "Error: unrecognized option: '%s'\n", + arg.c_str()); + exit(1); + } + } else { + out.childArgv.push_back(arg); + break; + } + } + for (; argi < argc; ++argi) { + out.childArgv.push_back(argv[argi]); + } + if (doShowKeys) { + debugShowKey(out.testAllowNonTtys); + exit(0); + } + if (out.childArgv.size() == 0) { + usage(program, 1); + } +} + +static std::string errorMessageToString(DWORD err) +{ + // Use FormatMessageW rather than FormatMessageA, because we want to use + // wcstombs to convert to the Cygwin locale, which might not match the + // codepage FormatMessageA would use. We need to convert using wcstombs, + // rather than print using %ls, because %ls doesn't work in the original + // MSYS. + wchar_t *wideMsgPtr = NULL; + const DWORD formatRet = FormatMessageW( + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + err, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(&wideMsgPtr), + 0, + NULL); + if (formatRet == 0 || wideMsgPtr == NULL) { + return std::string(); + } + std::string msg = wcsToMbs(wideMsgPtr); + LocalFree(wideMsgPtr); + const size_t pos = msg.find_last_not_of(" \r\n\t"); + if (pos == std::string::npos) { + msg.clear(); + } else { + msg.erase(pos + 1); + } + return msg; +} + +static std::string formatErrorMessage(DWORD err) +{ + char buf[64]; + sprintf(buf, "error %#x", static_cast(err)); + std::string ret = errorMessageToString(err); + if (ret.empty()) { + ret += buf; + } else { + ret += " ("; + ret += buf; + ret += ")"; + } + return ret; +} + +int main(int argc, char *argv[]) +{ + setlocale(LC_ALL, ""); + + g_mainWakeup = new WakeupFd(); + + Arguments args; + parseArguments(argc, argv, args); + + setupWin32Environment(); + + winsize sz = { 0 }; + sz.ws_col = 80; + sz.ws_row = 25; + ioctl(STDIN_FILENO, TIOCGWINSZ, &sz); + + DWORD agentFlags = WINPTY_FLAG_ALLOW_CURPROC_DESKTOP_CREATION; + if (args.testConerr) { agentFlags |= WINPTY_FLAG_CONERR; } + if (args.testPlainOutput) { agentFlags |= WINPTY_FLAG_PLAIN_OUTPUT; } + if (args.testColorEscapes) { agentFlags |= WINPTY_FLAG_COLOR_ESCAPES; } + winpty_config_t *agentCfg = winpty_config_new(agentFlags, NULL); + assert(agentCfg != NULL); + winpty_config_set_initial_size(agentCfg, sz.ws_col, sz.ws_row); + if (args.mouseInput) { + winpty_config_set_mouse_mode(agentCfg, WINPTY_MOUSE_MODE_FORCE); + } + + winpty_error_ptr_t openErr = NULL; + winpty_t *wp = winpty_open(agentCfg, &openErr); + if (wp == NULL) { + fprintf(stderr, "Error creating winpty: %s\n", + wcsToMbs(winpty_error_msg(openErr)).c_str()); + exit(1); + } + winpty_config_free(agentCfg); + winpty_error_free(openErr); + + HANDLE conin = CreateFileW(winpty_conin_name(wp), GENERIC_WRITE, 0, NULL, + OPEN_EXISTING, 0, NULL); + HANDLE conout = CreateFileW(winpty_conout_name(wp), GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL); + assert(conin != INVALID_HANDLE_VALUE); + assert(conout != INVALID_HANDLE_VALUE); + HANDLE conerr = NULL; + if (args.testConerr) { + conerr = CreateFileW(winpty_conerr_name(wp), GENERIC_READ, 0, NULL, + OPEN_EXISTING, 0, NULL); + assert(conerr != INVALID_HANDLE_VALUE); + } + + HANDLE childHandle = NULL; + + { + // Start the child process under the console. + args.childArgv[0] = findProgram(argv[0], args.childArgv[0]); + std::string cmdLine = argvToCommandLine(args.childArgv); + wchar_t *cmdLineW = heapMbsToWcs(cmdLine.c_str()); + + winpty_spawn_config_t *spawnCfg = winpty_spawn_config_new( + WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, + NULL, cmdLineW, NULL, NULL, NULL); + assert(spawnCfg != NULL); + + winpty_error_ptr_t spawnErr = NULL; + DWORD lastError = 0; + BOOL spawnRet = winpty_spawn(wp, spawnCfg, &childHandle, NULL, + &lastError, &spawnErr); + winpty_spawn_config_free(spawnCfg); + + if (!spawnRet) { + winpty_result_t spawnCode = winpty_error_code(spawnErr); + if (spawnCode == WINPTY_ERROR_SPAWN_CREATE_PROCESS_FAILED) { + fprintf(stderr, "%s: error: cannot start '%s': %s\n", + argv[0], + cmdLine.c_str(), + formatErrorMessage(lastError).c_str()); + } else { + fprintf(stderr, "%s: error: cannot start '%s': internal error: %s\n", + argv[0], + cmdLine.c_str(), + wcsToMbs(winpty_error_msg(spawnErr)).c_str()); + } + exit(1); + } + winpty_error_free(spawnErr); + delete [] cmdLineW; + } + + registerResizeSignalHandler(); + SavedTermiosMode mode = + setRawTerminalMode(args.testAllowNonTtys, true, args.testConerr); + + InputHandler inputHandler(conin, STDIN_FILENO, mainWakeup()); + OutputHandler outputHandler(conout, STDOUT_FILENO, mainWakeup()); + OutputHandler *errorHandler = NULL; + if (args.testConerr) { + errorHandler = new OutputHandler(conerr, STDERR_FILENO, mainWakeup()); + } + + while (true) { + fd_set readfds; + FD_ZERO(&readfds); + FD_SET(mainWakeup().fd(), &readfds); + selectWrapper("main thread", mainWakeup().fd() + 1, &readfds); + mainWakeup().reset(); + + // Check for terminal resize. + { + winsize sz2; + ioctl(STDIN_FILENO, TIOCGWINSZ, &sz2); + if (memcmp(&sz, &sz2, sizeof(sz)) != 0) { + sz = sz2; + winpty_set_size(wp, sz.ws_col, sz.ws_row, NULL); + } + } + + // Check for an I/O handler shutting down (possibly indicating that the + // child process has exited). + if (inputHandler.isComplete() || outputHandler.isComplete() || + (errorHandler != NULL && errorHandler->isComplete())) { + break; + } + } + + // Kill the agent connection. This will kill the agent, closing the CONIN + // and CONOUT pipes on the agent pipe, prompting our I/O handler to shut + // down. + winpty_free(wp); + + inputHandler.shutdown(); + outputHandler.shutdown(); + CloseHandle(conin); + CloseHandle(conout); + + if (errorHandler != NULL) { + errorHandler->shutdown(); + delete errorHandler; + CloseHandle(conerr); + } + + restoreTerminalMode(mode); + + DWORD exitCode = 0; + if (!GetExitCodeProcess(childHandle, &exitCode)) { + exitCode = 1; + } + CloseHandle(childHandle); + return exitCode; +}