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