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).
7 * This file is responsible for starting processes
8 * with pseudo-terminal file descriptors.
13 #include <Shlwapi.h> // PathCombine, PathIsRelative
21 #include "path_util.h"
26 extern "C" void init(v8::Local<v8::Object>);
28 #define WINPTY_DBG_VARIABLE TEXT("WINPTYDBG")
33 static std::vector<winpty_t *> ptyHandles;
34 static volatile LONG ptyCounter;
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) {
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);
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);
73 static NAN_METHOD(PtyGetExitCode) {
74 Nan::HandleScope scope;
76 if (info.Length() != 1 ||
77 !info[0]->IsNumber()) {
78 Nan::ThrowError("Usage: pty.getExitCode(pidHandle)");
83 GetExitCodeProcess((HANDLE)info[0]->IntegerValue(Nan::GetCurrentContext()).FromJust(), &exitCode);
85 info.GetReturnValue().Set(Nan::New<v8::Number>(exitCode));
88 static NAN_METHOD(PtyGetProcessList) {
89 Nan::HandleScope scope;
91 if (info.Length() != 1 ||
92 !info[0]->IsNumber()) {
93 Nan::ThrowError("Usage: pty.getProcessList(pid)");
97 int pid = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
99 winpty_t *pc = get_pipe_handle(pid);
101 info.GetReturnValue().Set(Nan::New<v8::Array>(0));
105 const int processCount = 64;
106 int actualCount = winpty_get_console_process_list(pc, processList, processCount, nullptr);
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]));
112 info.GetReturnValue().Set(result);
115 static NAN_METHOD(PtyStartProcess) {
116 Nan::HandleScope scope;
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)");
130 std::stringstream why;
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]));
136 // create environment block
138 const v8::Local<v8::Array> envValues = v8::Local<v8::Array>::Cast(info[2]);
139 if (!envValues.IsEmpty()) {
141 std::wstringstream envBlock;
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';
148 env = envBlock.str();
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);
157 shellpath = filename;
160 std::string shellpath_(shellpath.begin(), shellpath.end());
162 if (shellpath.empty() || !path_util::file_exists(shellpath)) {
163 why << "File not found: " << shellpath_;
164 Nan::ThrowError(why.str().c_str());
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();
172 // Enable/disable debugging
173 SetEnvironmentVariable(WINPTY_DBG_VARIABLE, debug ? "1" : NULL); // NULL = deletes variable
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);
182 winpty_error_free(error_ptr);
184 // Set pty size on config
185 winpty_config_set_initial_size(winpty_config, cols, rows);
187 // Start the pty agent
188 winpty_t *pc = winpty_open(winpty_config, &error_ptr);
189 winpty_config_free(winpty_config);
191 throw_winpty_error("Error launching WinPTY agent", error_ptr);
194 winpty_error_free(error_ptr);
196 // Save pty struct for later use
197 ptyHandles.insert(ptyHandles.end(), pc);
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);
205 winpty_error_free(error_ptr);
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);
212 throw_winpty_error("Unable to start terminal process", error_ptr);
215 winpty_error_free(error_ptr);
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));
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());
234 info.GetReturnValue().Set(marshal);
244 static NAN_METHOD(PtyResize) {
245 Nan::HandleScope scope;
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)");
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();
259 winpty_t *pc = get_pipe_handle(handle);
262 Nan::ThrowError("The pty doesn't appear to exist");
265 BOOL success = winpty_set_size(pc, cols, rows, nullptr);
267 Nan::ThrowError("The pty could not be resized");
271 return info.GetReturnValue().SetUndefined();
274 static NAN_METHOD(PtyKill) {
275 Nan::HandleScope scope;
277 if (info.Length() != 2 ||
278 !info[0]->IsNumber() ||
279 !info[1]->IsNumber()) {
280 Nan::ThrowError("Usage: pty.kill(pid, innerPidHandle)");
284 int handle = info[0]->Int32Value(Nan::GetCurrentContext()).FromJust();
285 HANDLE innerPidHandle = (HANDLE)info[1]->Int32Value(Nan::GetCurrentContext()).FromJust();
287 winpty_t *pc = get_pipe_handle(handle);
289 Nan::ThrowError("Pty seems to have been killed already");
293 assert(remove_pipe_handle(handle));
294 CloseHandle(innerPidHandle);
296 return info.GetReturnValue().SetUndefined();
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);
312 NODE_MODULE(pty, init);