Build NapCat Artifacts / Windows Loader Test (push) Canceled after 0s
Build NapCat Artifacts / Type Check & Test (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Framework (push) Canceled after 0s
Build NapCat Artifacts / Build NapCat.Shell (push) Canceled after 0s
730 lines
23 KiB
Plaintext
730 lines
23 KiB
Plaintext
// 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 <windows.h>
|
|
|
|
#include <assert.h>
|
|
#include <cygwin/version.h>
|
|
#include <errno.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/select.h>
|
|
#include <sys/cygwin.h>
|
|
#include <termios.h>
|
|
#include <unistd.h>
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include <winpty.h>
|
|
#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 <size_t N>
|
|
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<std::string> &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<std::string, std::string> 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<std::string, std::string>::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<std::string> 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] : "<program>";
|
|
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<wchar_t*>(&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<unsigned int>(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;
|
|
}
|