installed pty
[VSoRC/.git] / node_modules / node-pty / src / win / winpty.cc
1 /**
2  * Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
3  * Copyright (c) 2016, Daniel Imms (MIT License).
4  * Copyright (c) 2018, Microsoft Corporation (MIT License).
5  *
6  * pty.cc:
7  *   This file is responsible for starting processes
8  *   with pseudo-terminal file descriptors.
9  */
10
11 #include <iostream>
12 #include <nan.h>
13 #include <Shlwapi.h> // PathCombine, PathIsRelative
14 #include <sstream>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <string>
18 #include <vector>
19 #include <winpty.h>
20
21 #include "path_util.h"
22
23 /**
24 * Misc
25 */
26 extern "C" void init(v8::Local<v8::Object>);
27
28 #define WINPTY_DBG_VARIABLE TEXT("WINPTYDBG")
29
30 /**
31 * winpty
32 */
33 static std::vector<winpty_t *> ptyHandles;
34 static volatile LONG ptyCounter;
35
36 /**
37 * Helpers
38 */
39
40 static winpty_t *get_pipe_handle(int handle) {
41   for (size_t i = 0; i < ptyHandles.size(); ++i) {
42     winpty_t *ptyHandle = ptyHandles[i];
43     int current = (int)winpty_agent_process(ptyHandle);
44     if (current == handle) {
45       return ptyHandle;
46     }
47   }
48   return nullptr;
49 }
50
51 static bool remove_pipe_handle(int handle) {
52   for (size_t i = 0; i < ptyHandles.size(); ++i) {
53     winpty_t *ptyHandle = ptyHandles[i];
54     if ((int)winpty_agent_process(ptyHandle) == handle) {
55       winpty_free(ptyHandle);
56       ptyHandles.erase(ptyHandles.begin() + i);
57       ptyHandle = nullptr;
58       return true;
59     }
60   }
61   return false;
62 }
63
64 void throw_winpty_error(const char *generalMsg, winpty_error_ptr_t error_ptr) {
65   std::stringstream why;
66   std::wstring msg(winpty_error_msg(error_ptr));
67   std::string msg_(msg.begin(), msg.end());
68   why << generalMsg << ": " << msg_;
69   Nan::ThrowError(why.str().c_str());
70   winpty_error_free(error_ptr);
71 }
72
73 static NAN_METHOD(PtyGetExitCode) {
74   Nan::HandleScope scope;
75
76   if (info.Length() != 1 ||
77       !info[0]->IsNumber()) {
78     Nan::ThrowError("Usage: pty.getExitCode(pidHandle)");
79     return;
80   }
81
82   DWORD exitCode = 0;
83   GetExitCodeProcess((HANDLE)info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust(), &exitCode);
84
85   info.GetReturnValue().Set(Nan::New<v8::Number>(exitCode));
86 }
87
88 static NAN_METHOD(PtyGetProcessList) {
89   Nan::HandleScope scope;
90
91   if (info.Length() != 1 ||
92       !info[0]->IsNumber()) {
93     Nan::ThrowError("Usage: pty.getProcessList(pid)");
94     return;
95   }
96
97   int pid = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
98
99   winpty_t *pc = get_pipe_handle(pid);
100   if (pc == nullptr) {
101     info.GetReturnValue().Set(Nan::New<v8::Array>(0));
102     return;
103   }
104   int processList[64];
105   const int processCount = 64;
106   int actualCount = winpty_get_console_process_list(pc, processList, processCount, nullptr);
107
108   v8::Local<v8::Array> result = Nan::New<v8::Array>(actualCount);
109   for (uint32_t i = 0; i < actualCount; i++) {
110     Nan::Set(result, i, Nan::New<v8::Number>(processList[i]));
111   }
112   info.GetReturnValue().Set(result);
113 }
114
115 static NAN_METHOD(PtyStartProcess) {
116   Nan::HandleScope scope;
117
118   if (info.Length() != 7 ||
119       !info[0]->IsString() ||
120       !info[1]->IsString() ||
121       !info[2]->IsArray() ||
122       !info[3]->IsString() ||
123       !info[4]->IsNumber() ||
124       !info[5]->IsNumber() ||
125       !info[6]->IsBoolean()) {
126     Nan::ThrowError("Usage: pty.startProcess(file, cmdline, env, cwd, cols, rows, debug)");
127     return;
128   }
129
130   std::stringstream why;
131
132   const wchar_t *filename = path_util::to_wstring(Nan::Utf8String(info[0]));
133   const wchar_t *cmdline = path_util::to_wstring(Nan::Utf8String(info[1]));
134   const wchar_t *cwd = path_util::to_wstring(Nan::Utf8String(info[3]));
135
136   // create environment block
137   std::wstring env;
138   const v8::Local<v8::Array> envValues = v8::Local<v8::Array>::Cast(info[2]);
139   if (!envValues.IsEmpty()) {
140
141     std::wstringstream envBlock;
142
143     for(uint32_t i = 0; i < envValues->Length(); i++) {
144       std::wstring envValue(path_util::to_wstring(Nan::Utf8String(Nan::Get(envValues, i).ToLocalChecked())));
145       envBlock << envValue << L'\0';
146     }
147
148     env = envBlock.str();
149   }
150
151   // use environment 'Path' variable to determine location of
152   // the relative path that we have recieved (e.g cmd.exe)
153   std::wstring shellpath;
154   if (::PathIsRelativeW(filename)) {
155     shellpath = path_util::get_shell_path(filename);
156   } else {
157     shellpath = filename;
158   }
159
160   std::string shellpath_(shellpath.begin(), shellpath.end());
161
162   if (shellpath.empty() || !path_util::file_exists(shellpath)) {
163     why << "File not found: " << shellpath_;
164     Nan::ThrowError(why.str().c_str());
165     goto cleanup;
166   }
167
168   int cols = info[4]->Int32Value(Nan::GetCurrentContext()).FromJust();
169   int rows = info[5]->Int32Value(Nan::GetCurrentContext()).FromJust();
170   bool debug = Nan::To<bool>(info[6]).FromJust();
171
172   // Enable/disable debugging
173   SetEnvironmentVariable(WINPTY_DBG_VARIABLE, debug ? "1" : NULL); // NULL = deletes variable
174
175   // Create winpty config
176   winpty_error_ptr_t error_ptr = nullptr;
177   winpty_config_t* winpty_config = winpty_config_new(0, &error_ptr);
178   if (winpty_config == nullptr) {
179     throw_winpty_error("Error creating WinPTY config", error_ptr);
180     goto cleanup;
181   }
182   winpty_error_free(error_ptr);
183
184   // Set pty size on config
185   winpty_config_set_initial_size(winpty_config, cols, rows);
186
187   // Start the pty agent
188   winpty_t *pc = winpty_open(winpty_config, &error_ptr);
189   winpty_config_free(winpty_config);
190   if (pc == nullptr) {
191     throw_winpty_error("Error launching WinPTY agent", error_ptr);
192     goto cleanup;
193   }
194   winpty_error_free(error_ptr);
195
196   // Save pty struct for later use
197   ptyHandles.insert(ptyHandles.end(), pc);
198
199   // Create winpty spawn config
200   winpty_spawn_config_t* config = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, shellpath.c_str(), cmdline, cwd, env.c_str(), &error_ptr);
201   if (config == nullptr) {
202     throw_winpty_error("Error creating WinPTY spawn config", error_ptr);
203     goto cleanup;
204   }
205   winpty_error_free(error_ptr);
206
207   // Spawn the new process
208   HANDLE handle = nullptr;
209   BOOL spawnSuccess = winpty_spawn(pc, config, &handle, nullptr, nullptr, &error_ptr);
210   winpty_spawn_config_free(config);
211   if (!spawnSuccess) {
212     throw_winpty_error("Unable to start terminal process", error_ptr);
213     goto cleanup;
214   }
215   winpty_error_free(error_ptr);
216
217   // Set return values
218   v8::Local<v8::Object> marshal = Nan::New<v8::Object>();
219   Nan::Set(marshal, Nan::New<v8::String>("innerPid").ToLocalChecked(), Nan::New<v8::Number>((int)GetProcessId(handle)));
220   Nan::Set(marshal, Nan::New<v8::String>("innerPidHandle").ToLocalChecked(), Nan::New<v8::Number>((int)handle));
221   Nan::Set(marshal, Nan::New<v8::String>("pid").ToLocalChecked(), Nan::New<v8::Number>((int)winpty_agent_process(pc)));
222   Nan::Set(marshal, Nan::New<v8::String>("pty").ToLocalChecked(), Nan::New<v8::Number>(InterlockedIncrement(&ptyCounter)));
223   Nan::Set(marshal, Nan::New<v8::String>("fd").ToLocalChecked(), Nan::New<v8::Number>(-1));
224   {
225     LPCWSTR coninPipeName = winpty_conin_name(pc);
226     std::wstring coninPipeNameWStr(coninPipeName);
227     std::string coninPipeNameStr(coninPipeNameWStr.begin(), coninPipeNameWStr.end());
228     Nan::Set(marshal, Nan::New<v8::String>("conin").ToLocalChecked(), Nan::New<v8::String>(coninPipeNameStr).ToLocalChecked());
229     LPCWSTR conoutPipeName = winpty_conout_name(pc);
230     std::wstring conoutPipeNameWStr(conoutPipeName);
231     std::string conoutPipeNameStr(conoutPipeNameWStr.begin(), conoutPipeNameWStr.end());
232     Nan::Set(marshal, Nan::New<v8::String>("conout").ToLocalChecked(), Nan::New<v8::String>(conoutPipeNameStr).ToLocalChecked());
233   }
234   info.GetReturnValue().Set(marshal);
235
236   goto cleanup;
237
238 cleanup:
239   delete filename;
240   delete cmdline;
241   delete cwd;
242 }
243
244 static NAN_METHOD(PtyResize) {
245   Nan::HandleScope scope;
246
247   if (info.Length() != 3 ||
248       !info[0]->IsNumber() ||
249       !info[1]->IsNumber() ||
250       !info[2]->IsNumber()) {
251     Nan::ThrowError("Usage: pty.resize(pid, cols, rows)");
252     return;
253   }
254
255   int handle = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
256   int cols = info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
257   int rows = info[2]->Int32Value(Nan::GetCurrentContext()).FromJust();
258
259   winpty_t *pc = get_pipe_handle(handle);
260
261   if (pc == nullptr) {
262     Nan::ThrowError("The pty doesn't appear to exist");
263     return;
264   }
265   BOOL success = winpty_set_size(pc, cols, rows, nullptr);
266   if (!success) {
267     Nan::ThrowError("The pty could not be resized");
268     return;
269   }
270
271   return info.GetReturnValue().SetUndefined();
272 }
273
274 static NAN_METHOD(PtyKill) {
275   Nan::HandleScope scope;
276
277   if (info.Length() != 2 ||
278       !info[0]->IsNumber() ||
279       !info[1]->IsNumber()) {
280     Nan::ThrowError("Usage: pty.kill(pid, innerPidHandle)");
281     return;
282   }
283
284   int handle = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
285   HANDLE innerPidHandle = (HANDLE)info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
286
287   winpty_t *pc = get_pipe_handle(handle);
288   if (pc == nullptr) {
289     Nan::ThrowError("Pty seems to have been killed already");
290     return;
291   }
292
293   assert(remove_pipe_handle(handle));
294   CloseHandle(innerPidHandle);
295
296   return info.GetReturnValue().SetUndefined();
297 }
298
299 /**
300 * Init
301 */
302
303 extern "C" void init(v8::Local<v8::Object> target) {
304   Nan::HandleScope scope;
305   Nan::SetMethod(target, "startProcess", PtyStartProcess);
306   Nan::SetMethod(target, "resize", PtyResize);
307   Nan::SetMethod(target, "kill", PtyKill);
308   Nan::SetMethod(target, "getExitCode", PtyGetExitCode);
309   Nan::SetMethod(target, "getProcessList", PtyGetProcessList);
310 };
311
312 NODE_MODULE(pty, init);