"use strict"; /** * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License) * Copyright (c) 2016, Daniel Imms (MIT License). * Copyright (c) 2018, Microsoft Corporation (MIT License). */ Object.defineProperty(exports, "__esModule", { value: true }); var fs = require("fs"); var os = require("os"); var path = require("path"); var net_1 = require("net"); var child_process_1 = require("child_process"); var conptyNative; var winptyNative; /** * The amount of time to wait for additional data after the conpty shell process has exited before * shutting down the socket. The timer will be reset if a new data event comes in after the timer * has started. */ var FLUSH_DATA_INTERVAL = 20; /** * This agent sits between the WindowsTerminal class and provides a common interface for both conpty * and winpty. */ var WindowsPtyAgent = /** @class */ (function () { function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty, conptyInheritCursor) { var _this = this; if (conptyInheritCursor === void 0) { conptyInheritCursor = false; } this._useConpty = _useConpty; if (this._useConpty === undefined || this._useConpty === true) { this._useConpty = this._getWindowsBuildNumber() >= 18309; } if (this._useConpty) { if (!conptyNative) { try { conptyNative = require('../build/Release/conpty.node'); } catch (outerError) { try { conptyNative = require('../build/Debug/conpty.node'); } catch (innerError) { console.error('innerError', innerError); // Re-throw the exception from the Release require if the Debug require fails as well throw outerError; } } } } else { if (!winptyNative) { try { winptyNative = require('../build/Release/pty.node'); } catch (outerError) { try { winptyNative = require('../build/Debug/pty.node'); } catch (innerError) { console.error('innerError', innerError); // Re-throw the exception from the Release require if the Debug require fails as well throw outerError; } } } } this._ptyNative = this._useConpty ? conptyNative : winptyNative; // Sanitize input variable. cwd = path.resolve(cwd); // Compose command line var commandLine = argsToCommandLine(file, args); // Open pty session. var term; if (this._useConpty) { term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor); } else { term = this._ptyNative.startProcess(file, commandLine, env, cwd, cols, rows, debug); this._pid = term.pid; this._innerPid = term.innerPid; this._innerPidHandle = term.innerPidHandle; } // Not available on windows. this._fd = term.fd; // Generated incremental number that has no real purpose besides using it // as a terminal id. this._pty = term.pty; // Create terminal pipe IPC channel and forward to a local unix socket. this._outSocket = new net_1.Socket(); this._outSocket.setEncoding('utf8'); this._outSocket.connect(term.conout, function () { // TODO: Emit event on agent instead of socket? // Emit ready event. _this._outSocket.emit('ready_datapipe'); }); var inSocketFD = fs.openSync(term.conin, 'w'); this._inSocket = new net_1.Socket({ fd: inSocketFD, readable: false, writable: true }); this._inSocket.setEncoding('utf8'); // TODO: Wait for ready event? if (this._useConpty) { var connect = this._ptyNative.connect(this._pty, commandLine, cwd, env, function (c) { return _this._$onProcessExit(c); }); this._innerPid = connect.pid; } } Object.defineProperty(WindowsPtyAgent.prototype, "inSocket", { get: function () { return this._inSocket; }, enumerable: true, configurable: true }); Object.defineProperty(WindowsPtyAgent.prototype, "outSocket", { get: function () { return this._outSocket; }, enumerable: true, configurable: true }); Object.defineProperty(WindowsPtyAgent.prototype, "fd", { get: function () { return this._fd; }, enumerable: true, configurable: true }); Object.defineProperty(WindowsPtyAgent.prototype, "innerPid", { get: function () { return this._innerPid; }, enumerable: true, configurable: true }); Object.defineProperty(WindowsPtyAgent.prototype, "pty", { get: function () { return this._pty; }, enumerable: true, configurable: true }); WindowsPtyAgent.prototype.resize = function (cols, rows) { if (this._useConpty) { if (this._exitCode !== undefined) { throw new Error('Cannot resize a pty that has already exited'); } this._ptyNative.resize(this._pty, cols, rows); return; } this._ptyNative.resize(this._pid, cols, rows); }; WindowsPtyAgent.prototype.kill = function () { var _this = this; this._inSocket.readable = false; this._inSocket.writable = false; this._outSocket.readable = false; this._outSocket.writable = false; // Tell the agent to kill the pty, this releases handles to the process if (this._useConpty) { this._getConsoleProcessList().then(function (consoleProcessList) { consoleProcessList.forEach(function (pid) { try { process.kill(pid); } catch (e) { // Ignore if process cannot be found (kill ESRCH error) } }); _this._ptyNative.kill(_this._pty); }); } else { this._ptyNative.kill(this._pid, this._innerPidHandle); // Since pty.kill closes the handle it will kill most processes by itself // and process IDs can be reused as soon as all handles to them are // dropped, we want to immediately kill the entire console process list. // If we do not force kill all processes here, node servers in particular // seem to become detached and remain running (see // Microsoft/vscode#26807). var processList = this._ptyNative.getProcessList(this._pid); processList.forEach(function (pid) { try { process.kill(pid); } catch (e) { // Ignore if process cannot be found (kill ESRCH error) } }); } }; WindowsPtyAgent.prototype._getConsoleProcessList = function () { var _this = this; return new Promise(function (resolve) { var agent = child_process_1.fork(path.join(__dirname, 'conpty_console_list_agent'), [_this._innerPid.toString()]); agent.on('message', function (message) { clearTimeout(timeout); resolve(message.consoleProcessList); }); var timeout = setTimeout(function () { // Something went wrong, just send back the shell PID agent.kill(); resolve([_this._innerPid]); }, 5000); }); }; Object.defineProperty(WindowsPtyAgent.prototype, "exitCode", { get: function () { if (this._useConpty) { return this._exitCode; } return this._ptyNative.getExitCode(this._innerPidHandle); }, enumerable: true, configurable: true }); WindowsPtyAgent.prototype._getWindowsBuildNumber = function () { var osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); var buildNumber = 0; if (osVersion && osVersion.length === 4) { buildNumber = parseInt(osVersion[3]); } return buildNumber; }; WindowsPtyAgent.prototype._generatePipeName = function () { return "conpty-" + Math.random() * 10000000; }; /** * Triggered from the native side when a contpy process exits. */ WindowsPtyAgent.prototype._$onProcessExit = function (exitCode) { var _this = this; this._exitCode = exitCode; this._flushDataAndCleanUp(); this._outSocket.on('data', function () { return _this._flushDataAndCleanUp(); }); }; WindowsPtyAgent.prototype._flushDataAndCleanUp = function () { var _this = this; if (this._closeTimeout) { clearTimeout(this._closeTimeout); } this._closeTimeout = setTimeout(function () { return _this._cleanUpProcess(); }, FLUSH_DATA_INTERVAL); }; WindowsPtyAgent.prototype._cleanUpProcess = function () { this._inSocket.readable = false; this._inSocket.writable = false; this._outSocket.readable = false; this._outSocket.writable = false; this._outSocket.destroy(); }; return WindowsPtyAgent; }()); exports.WindowsPtyAgent = WindowsPtyAgent; // Convert argc/argv into a Win32 command-line following the escaping convention // documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from // winpty project. function argsToCommandLine(file, args) { if (isCommandLine(args)) { if (args.length === 0) { return file; } return argsToCommandLine(file, []) + " " + args; } var argv = [file]; Array.prototype.push.apply(argv, args); var result = ''; for (var argIndex = 0; argIndex < argv.length; argIndex++) { if (argIndex > 0) { result += ' '; } var arg = argv[argIndex]; // if it is empty or it contains whitespace and is not already quoted var hasLopsidedEnclosingQuote = xOr((arg[0] !== '"'), (arg[arg.length - 1] !== '"')); var hasNoEnclosingQuotes = ((arg[0] !== '"') && (arg[arg.length - 1] !== '"')); var quote = arg === '' || (arg.indexOf(' ') !== -1 || arg.indexOf('\t') !== -1) && ((arg.length > 1) && (hasLopsidedEnclosingQuote || hasNoEnclosingQuotes)); if (quote) { result += '\"'; } var bsCount = 0; for (var i = 0; i < arg.length; i++) { var p = arg[i]; if (p === '\\') { bsCount++; } else if (p === '"') { result += repeatText('\\', bsCount * 2 + 1); result += '"'; bsCount = 0; } else { result += repeatText('\\', bsCount); bsCount = 0; result += p; } } if (quote) { result += repeatText('\\', bsCount * 2); result += '\"'; } else { result += repeatText('\\', bsCount); } } return result; } exports.argsToCommandLine = argsToCommandLine; function isCommandLine(args) { return typeof args === 'string'; } function repeatText(text, count) { var result = ''; for (var i = 0; i < count; i++) { result += text; } return result; } function xOr(arg1, arg2) { return ((arg1 && !arg2) || (!arg1 && arg2)); } //# sourceMappingURL=windowsPtyAgent.js.map