UNPKG

12 kBJavaScriptView Raw
1"use strict";
2/**
3 * Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
4 * Copyright (c) 2016, Daniel Imms (MIT License).
5 * Copyright (c) 2018, Microsoft Corporation (MIT License).
6 */
7Object.defineProperty(exports, "__esModule", { value: true });
8var fs = require("fs");
9var os = require("os");
10var path = require("path");
11var net_1 = require("net");
12var child_process_1 = require("child_process");
13var conptyNative;
14var winptyNative;
15/**
16 * The amount of time to wait for additional data after the conpty shell process has exited before
17 * shutting down the socket. The timer will be reset if a new data event comes in after the timer
18 * has started.
19 */
20var FLUSH_DATA_INTERVAL = 20;
21/**
22 * This agent sits between the WindowsTerminal class and provides a common interface for both conpty
23 * and winpty.
24 */
25var WindowsPtyAgent = /** @class */ (function () {
26 function WindowsPtyAgent(file, args, env, cwd, cols, rows, debug, _useConpty, conptyInheritCursor) {
27 var _this = this;
28 if (conptyInheritCursor === void 0) { conptyInheritCursor = false; }
29 this._useConpty = _useConpty;
30 if (this._useConpty === undefined || this._useConpty === true) {
31 this._useConpty = this._getWindowsBuildNumber() >= 18309;
32 }
33 if (this._useConpty) {
34 if (!conptyNative) {
35 try {
36 conptyNative = require('../build/Release/conpty.node');
37 }
38 catch (outerError) {
39 try {
40 conptyNative = require('../build/Debug/conpty.node');
41 }
42 catch (innerError) {
43 console.error('innerError', innerError);
44 // Re-throw the exception from the Release require if the Debug require fails as well
45 throw outerError;
46 }
47 }
48 }
49 }
50 else {
51 if (!winptyNative) {
52 try {
53 winptyNative = require('../build/Release/pty.node');
54 }
55 catch (outerError) {
56 try {
57 winptyNative = require('../build/Debug/pty.node');
58 }
59 catch (innerError) {
60 console.error('innerError', innerError);
61 // Re-throw the exception from the Release require if the Debug require fails as well
62 throw outerError;
63 }
64 }
65 }
66 }
67 this._ptyNative = this._useConpty ? conptyNative : winptyNative;
68 // Sanitize input variable.
69 cwd = path.resolve(cwd);
70 // Compose command line
71 var commandLine = argsToCommandLine(file, args);
72 // Open pty session.
73 var term;
74 if (this._useConpty) {
75 term = this._ptyNative.startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor);
76 }
77 else {
78 term = this._ptyNative.startProcess(file, commandLine, env, cwd, cols, rows, debug);
79 this._pid = term.pid;
80 this._innerPid = term.innerPid;
81 this._innerPidHandle = term.innerPidHandle;
82 }
83 // Not available on windows.
84 this._fd = term.fd;
85 // Generated incremental number that has no real purpose besides using it
86 // as a terminal id.
87 this._pty = term.pty;
88 // Create terminal pipe IPC channel and forward to a local unix socket.
89 this._outSocket = new net_1.Socket();
90 this._outSocket.setEncoding('utf8');
91 this._outSocket.connect(term.conout, function () {
92 // TODO: Emit event on agent instead of socket?
93 // Emit ready event.
94 _this._outSocket.emit('ready_datapipe');
95 });
96 var inSocketFD = fs.openSync(term.conin, 'w');
97 this._inSocket = new net_1.Socket({
98 fd: inSocketFD,
99 readable: false,
100 writable: true
101 });
102 this._inSocket.setEncoding('utf8');
103 // TODO: Wait for ready event?
104 if (this._useConpty) {
105 var connect = this._ptyNative.connect(this._pty, commandLine, cwd, env, function (c) { return _this._$onProcessExit(c); });
106 this._innerPid = connect.pid;
107 }
108 }
109 Object.defineProperty(WindowsPtyAgent.prototype, "inSocket", {
110 get: function () { return this._inSocket; },
111 enumerable: true,
112 configurable: true
113 });
114 Object.defineProperty(WindowsPtyAgent.prototype, "outSocket", {
115 get: function () { return this._outSocket; },
116 enumerable: true,
117 configurable: true
118 });
119 Object.defineProperty(WindowsPtyAgent.prototype, "fd", {
120 get: function () { return this._fd; },
121 enumerable: true,
122 configurable: true
123 });
124 Object.defineProperty(WindowsPtyAgent.prototype, "innerPid", {
125 get: function () { return this._innerPid; },
126 enumerable: true,
127 configurable: true
128 });
129 Object.defineProperty(WindowsPtyAgent.prototype, "pty", {
130 get: function () { return this._pty; },
131 enumerable: true,
132 configurable: true
133 });
134 WindowsPtyAgent.prototype.resize = function (cols, rows) {
135 if (this._useConpty) {
136 if (this._exitCode !== undefined) {
137 throw new Error('Cannot resize a pty that has already exited');
138 }
139 this._ptyNative.resize(this._pty, cols, rows);
140 return;
141 }
142 this._ptyNative.resize(this._pid, cols, rows);
143 };
144 WindowsPtyAgent.prototype.kill = function () {
145 var _this = this;
146 this._inSocket.readable = false;
147 this._inSocket.writable = false;
148 this._outSocket.readable = false;
149 this._outSocket.writable = false;
150 // Tell the agent to kill the pty, this releases handles to the process
151 if (this._useConpty) {
152 this._getConsoleProcessList().then(function (consoleProcessList) {
153 consoleProcessList.forEach(function (pid) {
154 try {
155 process.kill(pid);
156 }
157 catch (e) {
158 // Ignore if process cannot be found (kill ESRCH error)
159 }
160 });
161 _this._ptyNative.kill(_this._pty);
162 });
163 }
164 else {
165 this._ptyNative.kill(this._pid, this._innerPidHandle);
166 // Since pty.kill closes the handle it will kill most processes by itself
167 // and process IDs can be reused as soon as all handles to them are
168 // dropped, we want to immediately kill the entire console process list.
169 // If we do not force kill all processes here, node servers in particular
170 // seem to become detached and remain running (see
171 // Microsoft/vscode#26807).
172 var processList = this._ptyNative.getProcessList(this._pid);
173 processList.forEach(function (pid) {
174 try {
175 process.kill(pid);
176 }
177 catch (e) {
178 // Ignore if process cannot be found (kill ESRCH error)
179 }
180 });
181 }
182 };
183 WindowsPtyAgent.prototype._getConsoleProcessList = function () {
184 var _this = this;
185 return new Promise(function (resolve) {
186 var agent = child_process_1.fork(path.join(__dirname, 'conpty_console_list_agent'), [_this._innerPid.toString()]);
187 agent.on('message', function (message) {
188 clearTimeout(timeout);
189 resolve(message.consoleProcessList);
190 });
191 var timeout = setTimeout(function () {
192 // Something went wrong, just send back the shell PID
193 agent.kill();
194 resolve([_this._innerPid]);
195 }, 5000);
196 });
197 };
198 Object.defineProperty(WindowsPtyAgent.prototype, "exitCode", {
199 get: function () {
200 if (this._useConpty) {
201 return this._exitCode;
202 }
203 return this._ptyNative.getExitCode(this._innerPidHandle);
204 },
205 enumerable: true,
206 configurable: true
207 });
208 WindowsPtyAgent.prototype._getWindowsBuildNumber = function () {
209 var osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
210 var buildNumber = 0;
211 if (osVersion && osVersion.length === 4) {
212 buildNumber = parseInt(osVersion[3]);
213 }
214 return buildNumber;
215 };
216 WindowsPtyAgent.prototype._generatePipeName = function () {
217 return "conpty-" + Math.random() * 10000000;
218 };
219 /**
220 * Triggered from the native side when a contpy process exits.
221 */
222 WindowsPtyAgent.prototype._$onProcessExit = function (exitCode) {
223 var _this = this;
224 this._exitCode = exitCode;
225 this._flushDataAndCleanUp();
226 this._outSocket.on('data', function () { return _this._flushDataAndCleanUp(); });
227 };
228 WindowsPtyAgent.prototype._flushDataAndCleanUp = function () {
229 var _this = this;
230 if (this._closeTimeout) {
231 clearTimeout(this._closeTimeout);
232 }
233 this._closeTimeout = setTimeout(function () { return _this._cleanUpProcess(); }, FLUSH_DATA_INTERVAL);
234 };
235 WindowsPtyAgent.prototype._cleanUpProcess = function () {
236 this._inSocket.readable = false;
237 this._inSocket.writable = false;
238 this._outSocket.readable = false;
239 this._outSocket.writable = false;
240 this._outSocket.destroy();
241 };
242 return WindowsPtyAgent;
243}());
244exports.WindowsPtyAgent = WindowsPtyAgent;
245// Convert argc/argv into a Win32 command-line following the escaping convention
246// documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from
247// winpty project.
248function argsToCommandLine(file, args) {
249 if (isCommandLine(args)) {
250 if (args.length === 0) {
251 return file;
252 }
253 return argsToCommandLine(file, []) + " " + args;
254 }
255 var argv = [file];
256 Array.prototype.push.apply(argv, args);
257 var result = '';
258 for (var argIndex = 0; argIndex < argv.length; argIndex++) {
259 if (argIndex > 0) {
260 result += ' ';
261 }
262 var arg = argv[argIndex];
263 // if it is empty or it contains whitespace and is not already quoted
264 var hasLopsidedEnclosingQuote = xOr((arg[0] !== '"'), (arg[arg.length - 1] !== '"'));
265 var hasNoEnclosingQuotes = ((arg[0] !== '"') && (arg[arg.length - 1] !== '"'));
266 var quote = arg === '' ||
267 (arg.indexOf(' ') !== -1 ||
268 arg.indexOf('\t') !== -1) &&
269 ((arg.length > 1) &&
270 (hasLopsidedEnclosingQuote || hasNoEnclosingQuotes));
271 if (quote) {
272 result += '\"';
273 }
274 var bsCount = 0;
275 for (var i = 0; i < arg.length; i++) {
276 var p = arg[i];
277 if (p === '\\') {
278 bsCount++;
279 }
280 else if (p === '"') {
281 result += repeatText('\\', bsCount * 2 + 1);
282 result += '"';
283 bsCount = 0;
284 }
285 else {
286 result += repeatText('\\', bsCount);
287 bsCount = 0;
288 result += p;
289 }
290 }
291 if (quote) {
292 result += repeatText('\\', bsCount * 2);
293 result += '\"';
294 }
295 else {
296 result += repeatText('\\', bsCount);
297 }
298 }
299 return result;
300}
301exports.argsToCommandLine = argsToCommandLine;
302function isCommandLine(args) {
303 return typeof args === 'string';
304}
305function repeatText(text, count) {
306 var result = '';
307 for (var i = 0; i < count; i++) {
308 result += text;
309 }
310 return result;
311}
312function xOr(arg1, arg2) {
313 return ((arg1 && !arg2) || (!arg1 && arg2));
314}
315//# sourceMappingURL=windowsPtyAgent.js.map
\No newline at end of file