arshjaved's picture
normal Model Upload (teja)
f9f1a35 verified
raw
history blame
24 kB
// 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;
}