node-pty 1.1.0-beta1 → 1.1.0-beta11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/binding.gyp +4 -15
- package/lib/unixTerminal.js +2 -1
- package/lib/unixTerminal.js.map +1 -1
- package/lib/unixTerminal.test.js +23 -0
- package/lib/unixTerminal.test.js.map +1 -1
- package/package.json +3 -2
- package/src/native.d.ts +1 -2
- package/src/unix/pty.cc +258 -289
- package/src/unixTerminal.test.ts +35 -0
- package/src/unixTerminal.ts +2 -1
- package/src/win/conpty.cc +182 -174
- package/src/win/conpty_console_list.cc +16 -15
- package/src/win/path_util.cc +16 -12
- package/src/win/path_util.h +5 -2
- package/src/win/winpty.cc +108 -151
package/src/unix/pty.cc
CHANGED
|
@@ -17,11 +17,14 @@
|
|
|
17
17
|
* Includes
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
#
|
|
20
|
+
#define NODE_ADDON_API_DISABLE_DEPRECATED
|
|
21
|
+
#include <napi.h>
|
|
22
|
+
#include <assert.h>
|
|
21
23
|
#include <errno.h>
|
|
22
24
|
#include <string.h>
|
|
23
25
|
#include <stdlib.h>
|
|
24
26
|
#include <unistd.h>
|
|
27
|
+
#include <thread>
|
|
25
28
|
|
|
26
29
|
#include <sys/types.h>
|
|
27
30
|
#include <sys/stat.h>
|
|
@@ -58,6 +61,7 @@
|
|
|
58
61
|
#include <paths.h>
|
|
59
62
|
#include <spawn.h>
|
|
60
63
|
#include <sys/event.h>
|
|
64
|
+
#include <sys/sysctl.h>
|
|
61
65
|
#include <termios.h>
|
|
62
66
|
#endif
|
|
63
67
|
|
|
@@ -98,27 +102,120 @@ int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
|
|
|
98
102
|
})
|
|
99
103
|
#endif
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
*/
|
|
104
|
-
|
|
105
|
-
struct pty_baton {
|
|
106
|
-
Nan::Persistent<v8::Function> cb;
|
|
107
|
-
int exit_code;
|
|
108
|
-
int signal_code;
|
|
109
|
-
pid_t pid;
|
|
110
|
-
uv_async_t async;
|
|
111
|
-
uv_thread_t tid;
|
|
105
|
+
struct ExitEvent {
|
|
106
|
+
int exit_code = 0, signal_code = 0;
|
|
112
107
|
};
|
|
113
108
|
|
|
109
|
+
void SetupExitCallback(Napi::Env env, Napi::Function cb, pid_t pid) {
|
|
110
|
+
std::thread *th = new std::thread;
|
|
111
|
+
// Don't use Napi::AsyncWorker which is limited by UV_THREADPOOL_SIZE.
|
|
112
|
+
auto tsfn = Napi::ThreadSafeFunction::New(
|
|
113
|
+
env,
|
|
114
|
+
cb, // JavaScript function called asynchronously
|
|
115
|
+
"SetupExitCallback_resource", // Name
|
|
116
|
+
0, // Unlimited queue
|
|
117
|
+
1, // Only one thread will use this initially
|
|
118
|
+
[th](Napi::Env) { // Finalizer used to clean threads up
|
|
119
|
+
th->join();
|
|
120
|
+
delete th;
|
|
121
|
+
});
|
|
122
|
+
*th = std::thread([tsfn = std::move(tsfn), pid] {
|
|
123
|
+
auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {
|
|
124
|
+
cb.Call({Napi::Number::New(env, exit_event->exit_code),
|
|
125
|
+
Napi::Number::New(env, exit_event->signal_code)});
|
|
126
|
+
delete exit_event;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
int ret;
|
|
130
|
+
int stat_loc;
|
|
131
|
+
#if defined(__APPLE__)
|
|
132
|
+
// Based on
|
|
133
|
+
// https://source.chromium.org/chromium/chromium/src/+/main:base/process/kill_mac.cc;l=35-69?
|
|
134
|
+
int kq = HANDLE_EINTR(kqueue());
|
|
135
|
+
struct kevent change = {0};
|
|
136
|
+
EV_SET(&change, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
|
|
137
|
+
ret = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));
|
|
138
|
+
if (ret == -1) {
|
|
139
|
+
if (errno == ESRCH) {
|
|
140
|
+
// At this point, one of the following has occurred:
|
|
141
|
+
// 1. The process has died but has not yet been reaped.
|
|
142
|
+
// 2. The process has died and has already been reaped.
|
|
143
|
+
// 3. The process is in the process of dying. It's no longer
|
|
144
|
+
// kqueueable, but it may not be waitable yet either. Mark calls
|
|
145
|
+
// this case the "zombie death race".
|
|
146
|
+
ret = HANDLE_EINTR(waitpid(pid, &stat_loc, WNOHANG));
|
|
147
|
+
if (ret == 0) {
|
|
148
|
+
ret = kill(pid, SIGKILL);
|
|
149
|
+
if (ret != -1) {
|
|
150
|
+
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
} else {
|
|
155
|
+
struct kevent event = {0};
|
|
156
|
+
ret = HANDLE_EINTR(kevent(kq, NULL, 0, &event, 1, NULL));
|
|
157
|
+
if (ret == 1) {
|
|
158
|
+
if ((event.fflags & NOTE_EXIT) &&
|
|
159
|
+
(event.ident == static_cast<uintptr_t>(pid))) {
|
|
160
|
+
// The process is dead or dying. This won't block for long, if at
|
|
161
|
+
// all.
|
|
162
|
+
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
#else
|
|
167
|
+
while (true) {
|
|
168
|
+
errno = 0;
|
|
169
|
+
if ((ret = waitpid(pid, &stat_loc, 0)) != pid) {
|
|
170
|
+
if (ret == -1 && errno == EINTR) {
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (ret == -1 && errno == ECHILD) {
|
|
174
|
+
// XXX node v0.8.x seems to have this problem.
|
|
175
|
+
// waitpid is already handled elsewhere.
|
|
176
|
+
;
|
|
177
|
+
} else {
|
|
178
|
+
assert(false);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
#endif
|
|
184
|
+
ExitEvent *exit_event = new ExitEvent;
|
|
185
|
+
if (WIFEXITED(stat_loc)) {
|
|
186
|
+
exit_event->exit_code = WEXITSTATUS(stat_loc); // errno?
|
|
187
|
+
}
|
|
188
|
+
if (WIFSIGNALED(stat_loc)) {
|
|
189
|
+
exit_event->signal_code = WTERMSIG(stat_loc);
|
|
190
|
+
}
|
|
191
|
+
auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
|
|
192
|
+
switch (status) {
|
|
193
|
+
case napi_closing:
|
|
194
|
+
break;
|
|
195
|
+
|
|
196
|
+
case napi_queue_full:
|
|
197
|
+
Napi::Error::Fatal("SetupExitCallback", "Queue was full");
|
|
198
|
+
|
|
199
|
+
case napi_ok:
|
|
200
|
+
if (tsfn.Release() != napi_ok) {
|
|
201
|
+
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.Release() failed");
|
|
202
|
+
}
|
|
203
|
+
break;
|
|
204
|
+
|
|
205
|
+
default:
|
|
206
|
+
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.BlockingCall() failed");
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
114
211
|
/**
|
|
115
212
|
* Methods
|
|
116
213
|
*/
|
|
117
214
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
215
|
+
Napi::Value PtyFork(const Napi::CallbackInfo& info);
|
|
216
|
+
Napi::Value PtyOpen(const Napi::CallbackInfo& info);
|
|
217
|
+
Napi::Value PtyResize(const Napi::CallbackInfo& info);
|
|
218
|
+
Napi::Value PtyGetProc(const Napi::CallbackInfo& info);
|
|
122
219
|
|
|
123
220
|
/**
|
|
124
221
|
* Functions
|
|
@@ -135,15 +232,6 @@ static char *
|
|
|
135
232
|
pty_getproc(int, char *);
|
|
136
233
|
#endif
|
|
137
234
|
|
|
138
|
-
static void
|
|
139
|
-
pty_waitpid(void *);
|
|
140
|
-
|
|
141
|
-
static void
|
|
142
|
-
pty_after_waitpid(uv_async_t *);
|
|
143
|
-
|
|
144
|
-
static void
|
|
145
|
-
pty_after_close(uv_handle_t *);
|
|
146
|
-
|
|
147
235
|
#if defined(__APPLE__) || defined(__OpenBSD__)
|
|
148
236
|
static void
|
|
149
237
|
pty_posix_spawn(char** argv, char** env,
|
|
@@ -154,62 +242,75 @@ pty_posix_spawn(char** argv, char** env,
|
|
|
154
242
|
int* err);
|
|
155
243
|
#endif
|
|
156
244
|
|
|
157
|
-
|
|
158
|
-
|
|
245
|
+
struct DelBuf {
|
|
246
|
+
int len;
|
|
247
|
+
DelBuf(int len) : len(len) {}
|
|
248
|
+
void operator()(char **p) {
|
|
249
|
+
if (p == nullptr)
|
|
250
|
+
return;
|
|
251
|
+
for (int i = 0; i < len; i++)
|
|
252
|
+
free(p[i]);
|
|
253
|
+
delete[] p;
|
|
254
|
+
}
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
Napi::Value PtyFork(const Napi::CallbackInfo& info) {
|
|
258
|
+
Napi::Env napiEnv(info.Env());
|
|
259
|
+
Napi::HandleScope scope(napiEnv);
|
|
159
260
|
|
|
160
261
|
if (info.Length() != 11 ||
|
|
161
|
-
!info[0]
|
|
162
|
-
!info[1]
|
|
163
|
-
!info[2]
|
|
164
|
-
!info[3]
|
|
165
|
-
!info[4]
|
|
166
|
-
!info[5]
|
|
167
|
-
!info[6]
|
|
168
|
-
!info[7]
|
|
169
|
-
!info[8]
|
|
170
|
-
!info[9]
|
|
171
|
-
!info[10]
|
|
172
|
-
|
|
173
|
-
"Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, helperPath, onexit)");
|
|
262
|
+
!info[0].IsString() ||
|
|
263
|
+
!info[1].IsArray() ||
|
|
264
|
+
!info[2].IsArray() ||
|
|
265
|
+
!info[3].IsString() ||
|
|
266
|
+
!info[4].IsNumber() ||
|
|
267
|
+
!info[5].IsNumber() ||
|
|
268
|
+
!info[6].IsNumber() ||
|
|
269
|
+
!info[7].IsNumber() ||
|
|
270
|
+
!info[8].IsBoolean() ||
|
|
271
|
+
!info[9].IsString() ||
|
|
272
|
+
!info[10].IsFunction()) {
|
|
273
|
+
throw Napi::Error::New(napiEnv, "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, helperPath, onexit)");
|
|
174
274
|
}
|
|
175
275
|
|
|
176
276
|
// file
|
|
177
|
-
|
|
277
|
+
std::string file = info[0].As<Napi::String>();
|
|
178
278
|
|
|
179
279
|
// args
|
|
180
|
-
|
|
280
|
+
Napi::Array argv_ = info[1].As<Napi::Array>();
|
|
181
281
|
|
|
182
282
|
// env
|
|
183
|
-
|
|
184
|
-
int envc = env_
|
|
185
|
-
char
|
|
283
|
+
Napi::Array env_ = info[2].As<Napi::Array>();
|
|
284
|
+
int envc = env_.Length();
|
|
285
|
+
std::unique_ptr<char *, DelBuf> env_unique_ptr(new char *[envc + 1], DelBuf(envc + 1));
|
|
286
|
+
char **env = env_unique_ptr.get();
|
|
186
287
|
env[envc] = NULL;
|
|
187
288
|
for (int i = 0; i < envc; i++) {
|
|
188
|
-
|
|
189
|
-
env[i] = strdup(
|
|
289
|
+
std::string pair = env_.Get(i).As<Napi::String>();
|
|
290
|
+
env[i] = strdup(pair.c_str());
|
|
190
291
|
}
|
|
191
292
|
|
|
192
293
|
// cwd
|
|
193
|
-
|
|
294
|
+
std::string cwd_ = info[3].As<Napi::String>();
|
|
194
295
|
|
|
195
296
|
// size
|
|
196
297
|
struct winsize winp;
|
|
197
|
-
winp.ws_col = info[4]
|
|
198
|
-
winp.ws_row = info[5]
|
|
298
|
+
winp.ws_col = info[4].As<Napi::Number>().Int32Value();
|
|
299
|
+
winp.ws_row = info[5].As<Napi::Number>().Int32Value();
|
|
199
300
|
winp.ws_xpixel = 0;
|
|
200
301
|
winp.ws_ypixel = 0;
|
|
201
302
|
|
|
202
303
|
#if !defined(__APPLE__)
|
|
203
304
|
// uid / gid
|
|
204
|
-
int uid = info[6]
|
|
205
|
-
int gid = info[7]
|
|
305
|
+
int uid = info[6].As<Napi::Number>().Int32Value();
|
|
306
|
+
int gid = info[7].As<Napi::Number>().Int32Value();
|
|
206
307
|
#endif
|
|
207
308
|
|
|
208
309
|
// termios
|
|
209
310
|
struct termios t = termios();
|
|
210
311
|
struct termios *term = &t;
|
|
211
312
|
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
|
|
212
|
-
if (
|
|
313
|
+
if (info[8].As<Napi::Boolean>().Value()) {
|
|
213
314
|
#if defined(IUTF8)
|
|
214
315
|
term->c_iflag |= IUTF8;
|
|
215
316
|
#endif
|
|
@@ -244,45 +345,44 @@ NAN_METHOD(PtyFork) {
|
|
|
244
345
|
cfsetospeed(term, B38400);
|
|
245
346
|
|
|
246
347
|
// helperPath
|
|
247
|
-
|
|
348
|
+
std::string helper_path = info[9].As<Napi::String>();
|
|
248
349
|
|
|
249
350
|
pid_t pid;
|
|
250
351
|
int master;
|
|
251
352
|
#if defined(__APPLE__)
|
|
252
|
-
int argc = argv_
|
|
353
|
+
int argc = argv_.Length();
|
|
253
354
|
int argl = argc + 4;
|
|
254
|
-
char
|
|
255
|
-
argv
|
|
256
|
-
argv[
|
|
257
|
-
argv[
|
|
355
|
+
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
|
|
356
|
+
char **argv = argv_unique_ptr.get();
|
|
357
|
+
argv[0] = strdup(helper_path.c_str());
|
|
358
|
+
argv[1] = strdup(cwd_.c_str());
|
|
359
|
+
argv[2] = strdup(file.c_str());
|
|
258
360
|
argv[argl - 1] = NULL;
|
|
259
361
|
for (int i = 0; i < argc; i++) {
|
|
260
|
-
|
|
261
|
-
argv[i + 3] = strdup(
|
|
362
|
+
std::string arg = argv_.Get(i).As<Napi::String>();
|
|
363
|
+
argv[i + 3] = strdup(arg.c_str());
|
|
262
364
|
}
|
|
263
365
|
|
|
264
366
|
int err = -1;
|
|
265
367
|
pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err);
|
|
266
368
|
if (err != 0) {
|
|
267
|
-
|
|
268
|
-
goto done;
|
|
369
|
+
throw Napi::Error::New(napiEnv, "posix_spawnp failed.");
|
|
269
370
|
}
|
|
270
371
|
if (pty_nonblock(master) == -1) {
|
|
271
|
-
|
|
272
|
-
goto done;
|
|
372
|
+
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
|
|
273
373
|
}
|
|
274
374
|
#else
|
|
275
|
-
int argc = argv_
|
|
375
|
+
int argc = argv_.Length();
|
|
276
376
|
int argl = argc + 2;
|
|
277
|
-
char
|
|
278
|
-
argv
|
|
377
|
+
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
|
|
378
|
+
char** argv = argv_unique_ptr.get();
|
|
379
|
+
argv[0] = strdup(file.c_str());
|
|
279
380
|
argv[argl - 1] = NULL;
|
|
280
381
|
for (int i = 0; i < argc; i++) {
|
|
281
|
-
|
|
282
|
-
argv[i + 1] = strdup(
|
|
382
|
+
std::string arg = argv_.Get(i).As<Napi::String>();
|
|
383
|
+
argv[i + 1] = strdup(arg.c_str());
|
|
283
384
|
}
|
|
284
385
|
|
|
285
|
-
char* cwd = strdup(*cwd_);
|
|
286
386
|
sigset_t newmask, oldmask;
|
|
287
387
|
struct sigaction sig_action;
|
|
288
388
|
// temporarily block all signals
|
|
@@ -302,14 +402,6 @@ NAN_METHOD(PtyFork) {
|
|
|
302
402
|
for (int i = 0 ; i < NSIG ; i++) { // NSIG is a macro for all signals + 1
|
|
303
403
|
sigaction(i, &sig_action, NULL);
|
|
304
404
|
}
|
|
305
|
-
} else {
|
|
306
|
-
for (int i = 0; i < argl; i++) free(argv[i]);
|
|
307
|
-
delete[] argv;
|
|
308
|
-
|
|
309
|
-
for (int i = 0; i < envc; i++) free(env[i]);
|
|
310
|
-
delete[] env;
|
|
311
|
-
|
|
312
|
-
free(cwd);
|
|
313
405
|
}
|
|
314
406
|
|
|
315
407
|
// reenable signals
|
|
@@ -317,11 +409,10 @@ NAN_METHOD(PtyFork) {
|
|
|
317
409
|
|
|
318
410
|
switch (pid) {
|
|
319
411
|
case -1:
|
|
320
|
-
|
|
321
|
-
goto done;
|
|
412
|
+
throw Napi::Error::New(napiEnv, "forkpty(3) failed.");
|
|
322
413
|
case 0:
|
|
323
|
-
if (strlen(
|
|
324
|
-
if (chdir(
|
|
414
|
+
if (strlen(cwd_.c_str())) {
|
|
415
|
+
if (chdir(cwd_.c_str()) == -1) {
|
|
325
416
|
perror("chdir(2) failed.");
|
|
326
417
|
_exit(1);
|
|
327
418
|
}
|
|
@@ -348,62 +439,36 @@ NAN_METHOD(PtyFork) {
|
|
|
348
439
|
}
|
|
349
440
|
default:
|
|
350
441
|
if (pty_nonblock(master) == -1) {
|
|
351
|
-
|
|
352
|
-
goto done;
|
|
442
|
+
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
|
|
353
443
|
}
|
|
354
444
|
}
|
|
355
445
|
#endif
|
|
356
446
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
Nan::New<v8::Number>(master));
|
|
362
|
-
Nan::Set(obj,
|
|
363
|
-
Nan::New<v8::String>("pid").ToLocalChecked(),
|
|
364
|
-
Nan::New<v8::Number>(pid));
|
|
365
|
-
Nan::Set(obj,
|
|
366
|
-
Nan::New<v8::String>("pty").ToLocalChecked(),
|
|
367
|
-
Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
|
|
368
|
-
|
|
369
|
-
pty_baton *baton = new pty_baton();
|
|
370
|
-
baton->exit_code = 0;
|
|
371
|
-
baton->signal_code = 0;
|
|
372
|
-
baton->cb.Reset(v8::Local<v8::Function>::Cast(info[10]));
|
|
373
|
-
baton->pid = pid;
|
|
374
|
-
baton->async.data = baton;
|
|
375
|
-
|
|
376
|
-
uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid);
|
|
377
|
-
|
|
378
|
-
uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton));
|
|
379
|
-
|
|
380
|
-
return info.GetReturnValue().Set(obj);
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
done:
|
|
384
|
-
#if defined(__APPLE__)
|
|
385
|
-
for (int i = 0; i < argl; i++) free(argv[i]);
|
|
386
|
-
delete[] argv;
|
|
447
|
+
Napi::Object obj = Napi::Object::New(napiEnv);
|
|
448
|
+
obj.Set("fd", Napi::Number::New(napiEnv, master));
|
|
449
|
+
obj.Set("pid", Napi::Number::New(napiEnv, pid));
|
|
450
|
+
obj.Set("pty", Napi::String::New(napiEnv, ptsname(master)));
|
|
387
451
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return
|
|
452
|
+
// Set up process exit callback.
|
|
453
|
+
Napi::Function cb = info[10].As<Napi::Function>();
|
|
454
|
+
SetupExitCallback(napiEnv, cb, pid);
|
|
455
|
+
return obj;
|
|
392
456
|
}
|
|
393
457
|
|
|
394
|
-
|
|
395
|
-
|
|
458
|
+
Napi::Value PtyOpen(const Napi::CallbackInfo& info) {
|
|
459
|
+
Napi::Env env(info.Env());
|
|
460
|
+
Napi::HandleScope scope(env);
|
|
396
461
|
|
|
397
462
|
if (info.Length() != 2 ||
|
|
398
|
-
!info[0]
|
|
399
|
-
!info[1]
|
|
400
|
-
|
|
463
|
+
!info[0].IsNumber() ||
|
|
464
|
+
!info[1].IsNumber()) {
|
|
465
|
+
throw Napi::Error::New(env, "Usage: pty.open(cols, rows)");
|
|
401
466
|
}
|
|
402
467
|
|
|
403
468
|
// size
|
|
404
469
|
struct winsize winp;
|
|
405
|
-
winp.ws_col = info[0]
|
|
406
|
-
winp.ws_row = info[1]
|
|
470
|
+
winp.ws_col = info[0].As<Napi::Number>().Int32Value();
|
|
471
|
+
winp.ws_row = info[1].As<Napi::Number>().Int32Value();
|
|
407
472
|
winp.ws_xpixel = 0;
|
|
408
473
|
winp.ws_ypixel = 0;
|
|
409
474
|
|
|
@@ -412,98 +477,98 @@ NAN_METHOD(PtyOpen) {
|
|
|
412
477
|
int ret = openpty(&master, &slave, nullptr, NULL, static_cast<winsize*>(&winp));
|
|
413
478
|
|
|
414
479
|
if (ret == -1) {
|
|
415
|
-
|
|
480
|
+
throw Napi::Error::New(env, "openpty(3) failed.");
|
|
416
481
|
}
|
|
417
482
|
|
|
418
483
|
if (pty_nonblock(master) == -1) {
|
|
419
|
-
|
|
484
|
+
throw Napi::Error::New(env, "Could not set master fd to nonblocking.");
|
|
420
485
|
}
|
|
421
486
|
|
|
422
487
|
if (pty_nonblock(slave) == -1) {
|
|
423
|
-
|
|
488
|
+
throw Napi::Error::New(env, "Could not set slave fd to nonblocking.");
|
|
424
489
|
}
|
|
425
490
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
Nan::Set(obj,
|
|
431
|
-
Nan::New<v8::String>("slave").ToLocalChecked(),
|
|
432
|
-
Nan::New<v8::Number>(slave));
|
|
433
|
-
Nan::Set(obj,
|
|
434
|
-
Nan::New<v8::String>("pty").ToLocalChecked(),
|
|
435
|
-
Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
|
|
491
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
492
|
+
obj.Set("master", Napi::Number::New(env, master));
|
|
493
|
+
obj.Set("slave", Napi::Number::New(env, slave));
|
|
494
|
+
obj.Set("pty", Napi::String::New(env, ptsname(master)));
|
|
436
495
|
|
|
437
|
-
return
|
|
496
|
+
return obj;
|
|
438
497
|
}
|
|
439
498
|
|
|
440
|
-
|
|
441
|
-
|
|
499
|
+
Napi::Value PtyResize(const Napi::CallbackInfo& info) {
|
|
500
|
+
Napi::Env env(info.Env());
|
|
501
|
+
Napi::HandleScope scope(env);
|
|
442
502
|
|
|
443
503
|
if (info.Length() != 3 ||
|
|
444
|
-
!info[0]
|
|
445
|
-
!info[1]
|
|
446
|
-
!info[2]
|
|
447
|
-
|
|
504
|
+
!info[0].IsNumber() ||
|
|
505
|
+
!info[1].IsNumber() ||
|
|
506
|
+
!info[2].IsNumber()) {
|
|
507
|
+
throw Napi::Error::New(env, "Usage: pty.resize(fd, cols, rows)");
|
|
448
508
|
}
|
|
449
509
|
|
|
450
|
-
int fd = info[0]
|
|
510
|
+
int fd = info[0].As<Napi::Number>().Int32Value();
|
|
451
511
|
|
|
452
512
|
struct winsize winp;
|
|
453
|
-
winp.ws_col = info[1]
|
|
454
|
-
winp.ws_row = info[2]
|
|
513
|
+
winp.ws_col = info[1].As<Napi::Number>().Int32Value();
|
|
514
|
+
winp.ws_row = info[2].As<Napi::Number>().Int32Value();
|
|
455
515
|
winp.ws_xpixel = 0;
|
|
456
516
|
winp.ws_ypixel = 0;
|
|
457
517
|
|
|
458
518
|
if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
|
|
459
519
|
switch (errno) {
|
|
460
|
-
case EBADF:
|
|
461
|
-
|
|
462
|
-
case
|
|
463
|
-
|
|
520
|
+
case EBADF:
|
|
521
|
+
throw Napi::Error::New(env, "ioctl(2) failed, EBADF");
|
|
522
|
+
case EFAULT:
|
|
523
|
+
throw Napi::Error::New(env, "ioctl(2) failed, EFAULT");
|
|
524
|
+
case EINVAL:
|
|
525
|
+
throw Napi::Error::New(env, "ioctl(2) failed, EINVAL");
|
|
526
|
+
case ENOTTY:
|
|
527
|
+
throw Napi::Error::New(env, "ioctl(2) failed, ENOTTY");
|
|
464
528
|
}
|
|
465
|
-
|
|
529
|
+
throw Napi::Error::New(env, "ioctl(2) failed");
|
|
466
530
|
}
|
|
467
531
|
|
|
468
|
-
return
|
|
532
|
+
return env.Undefined();
|
|
469
533
|
}
|
|
470
534
|
|
|
471
535
|
/**
|
|
472
536
|
* Foreground Process Name
|
|
473
537
|
*/
|
|
474
|
-
|
|
475
|
-
|
|
538
|
+
Napi::Value PtyGetProc(const Napi::CallbackInfo& info) {
|
|
539
|
+
Napi::Env env(info.Env());
|
|
540
|
+
Napi::HandleScope scope(env);
|
|
476
541
|
|
|
477
542
|
#if defined(__APPLE__)
|
|
478
543
|
if (info.Length() != 1 ||
|
|
479
|
-
!info[0]
|
|
480
|
-
|
|
544
|
+
!info[0].IsNumber()) {
|
|
545
|
+
throw Napi::Error::New(env, "Usage: pty.process(pid)");
|
|
481
546
|
}
|
|
482
547
|
|
|
483
|
-
int
|
|
484
|
-
char *name = pty_getproc(
|
|
548
|
+
int fd = info[0].As<Napi::Number>().Int32Value();
|
|
549
|
+
char *name = pty_getproc(fd);
|
|
485
550
|
#else
|
|
486
551
|
if (info.Length() != 2 ||
|
|
487
|
-
!info[0]
|
|
488
|
-
!info[1]
|
|
489
|
-
|
|
552
|
+
!info[0].IsNumber() ||
|
|
553
|
+
!info[1].IsString()) {
|
|
554
|
+
throw Napi::Error::New(env, "Usage: pty.process(fd, tty)");
|
|
490
555
|
}
|
|
491
556
|
|
|
492
|
-
int fd = info[0]
|
|
557
|
+
int fd = info[0].As<Napi::Number>().Int32Value();
|
|
493
558
|
|
|
494
|
-
|
|
495
|
-
char *tty = strdup(
|
|
559
|
+
std::string tty_ = info[1].As<Napi::String>();
|
|
560
|
+
char *tty = strdup(tty_.c_str());
|
|
496
561
|
char *name = pty_getproc(fd, tty);
|
|
497
562
|
free(tty);
|
|
498
563
|
#endif
|
|
499
564
|
|
|
500
565
|
if (name == NULL) {
|
|
501
|
-
return
|
|
566
|
+
return env.Undefined();
|
|
502
567
|
}
|
|
503
568
|
|
|
504
|
-
|
|
569
|
+
Napi::String name_ = Napi::String::New(env, name);
|
|
505
570
|
free(name);
|
|
506
|
-
return
|
|
571
|
+
return name_;
|
|
507
572
|
}
|
|
508
573
|
|
|
509
574
|
/**
|
|
@@ -517,114 +582,6 @@ pty_nonblock(int fd) {
|
|
|
517
582
|
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
518
583
|
}
|
|
519
584
|
|
|
520
|
-
/**
|
|
521
|
-
* pty_waitpid
|
|
522
|
-
* Wait for SIGCHLD to read exit status.
|
|
523
|
-
*/
|
|
524
|
-
|
|
525
|
-
static void
|
|
526
|
-
pty_waitpid(void *data) {
|
|
527
|
-
int ret;
|
|
528
|
-
int stat_loc;
|
|
529
|
-
pty_baton *baton = static_cast<pty_baton*>(data);
|
|
530
|
-
errno = 0;
|
|
531
|
-
#if defined(__APPLE__)
|
|
532
|
-
// Based on
|
|
533
|
-
// https://source.chromium.org/chromium/chromium/src/+/main:base/process/kill_mac.cc;l=35-69?
|
|
534
|
-
int kq = HANDLE_EINTR(kqueue());
|
|
535
|
-
struct kevent change = {0};
|
|
536
|
-
EV_SET(&change, baton->pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
|
|
537
|
-
ret = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));
|
|
538
|
-
if (ret == -1) {
|
|
539
|
-
if (errno == ESRCH) {
|
|
540
|
-
// At this point, one of the following has occurred:
|
|
541
|
-
// 1. The process has died but has not yet been reaped.
|
|
542
|
-
// 2. The process has died and has already been reaped.
|
|
543
|
-
// 3. The process is in the process of dying. It's no longer
|
|
544
|
-
// kqueueable, but it may not be waitable yet either. Mark calls
|
|
545
|
-
// this case the "zombie death race".
|
|
546
|
-
ret = HANDLE_EINTR(waitpid(baton->pid, &stat_loc, WNOHANG));
|
|
547
|
-
if (ret == 0) {
|
|
548
|
-
ret = kill(baton->pid, SIGKILL);
|
|
549
|
-
if (ret != -1) {
|
|
550
|
-
HANDLE_EINTR(waitpid(baton->pid, &stat_loc, 0));
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
}
|
|
554
|
-
} else {
|
|
555
|
-
struct kevent event = {0};
|
|
556
|
-
ret = HANDLE_EINTR(kevent(kq, NULL, 0, &event, 1, NULL));
|
|
557
|
-
if (ret == 1) {
|
|
558
|
-
if ((event.fflags & NOTE_EXIT) &&
|
|
559
|
-
(event.ident == static_cast<uintptr_t>(baton->pid))) {
|
|
560
|
-
// The process is dead or dying. This won't block for long, if at
|
|
561
|
-
// all.
|
|
562
|
-
HANDLE_EINTR(waitpid(baton->pid, &stat_loc, 0));
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
}
|
|
566
|
-
#else
|
|
567
|
-
if ((ret = waitpid(baton->pid, &stat_loc, 0)) != baton->pid) {
|
|
568
|
-
if (ret == -1 && errno == EINTR) {
|
|
569
|
-
return pty_waitpid(baton);
|
|
570
|
-
}
|
|
571
|
-
if (ret == -1 && errno == ECHILD) {
|
|
572
|
-
// XXX node v0.8.x seems to have this problem.
|
|
573
|
-
// waitpid is already handled elsewhere.
|
|
574
|
-
;
|
|
575
|
-
} else {
|
|
576
|
-
assert(false);
|
|
577
|
-
}
|
|
578
|
-
}
|
|
579
|
-
#endif
|
|
580
|
-
|
|
581
|
-
if (WIFEXITED(stat_loc)) {
|
|
582
|
-
baton->exit_code = WEXITSTATUS(stat_loc); // errno?
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
if (WIFSIGNALED(stat_loc)) {
|
|
586
|
-
baton->signal_code = WTERMSIG(stat_loc);
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
uv_async_send(&baton->async);
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
/**
|
|
593
|
-
* pty_after_waitpid
|
|
594
|
-
* Callback after exit status has been read.
|
|
595
|
-
*/
|
|
596
|
-
|
|
597
|
-
static void
|
|
598
|
-
pty_after_waitpid(uv_async_t *async) {
|
|
599
|
-
Nan::HandleScope scope;
|
|
600
|
-
pty_baton *baton = static_cast<pty_baton*>(async->data);
|
|
601
|
-
|
|
602
|
-
v8::Local<v8::Value> argv[] = {
|
|
603
|
-
Nan::New<v8::Integer>(baton->exit_code),
|
|
604
|
-
Nan::New<v8::Integer>(baton->signal_code),
|
|
605
|
-
};
|
|
606
|
-
|
|
607
|
-
v8::Local<v8::Function> cb = Nan::New<v8::Function>(baton->cb);
|
|
608
|
-
baton->cb.Reset();
|
|
609
|
-
memset(&baton->cb, -1, sizeof(baton->cb));
|
|
610
|
-
Nan::AsyncResource resource("pty_after_waitpid");
|
|
611
|
-
resource.runInAsyncScope(Nan::GetCurrentContext()->Global(), cb, 2, argv);
|
|
612
|
-
|
|
613
|
-
uv_close((uv_handle_t *)async, pty_after_close);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
/**
|
|
617
|
-
* pty_after_close
|
|
618
|
-
* uv_close() callback - free handle data
|
|
619
|
-
*/
|
|
620
|
-
|
|
621
|
-
static void
|
|
622
|
-
pty_after_close(uv_handle_t *handle) {
|
|
623
|
-
uv_async_t *async = (uv_async_t *)handle;
|
|
624
|
-
pty_baton *baton = static_cast<pty_baton*>(async->data);
|
|
625
|
-
delete baton;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
585
|
/**
|
|
629
586
|
* pty_getproc
|
|
630
587
|
* Taken from tmux.
|
|
@@ -692,13 +649,25 @@ pty_getproc(int fd, char *tty) {
|
|
|
692
649
|
#elif defined(__APPLE__)
|
|
693
650
|
|
|
694
651
|
static char *
|
|
695
|
-
pty_getproc(int
|
|
696
|
-
|
|
697
|
-
|
|
652
|
+
pty_getproc(int fd) {
|
|
653
|
+
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
|
|
654
|
+
size_t size;
|
|
655
|
+
struct kinfo_proc kp;
|
|
656
|
+
|
|
657
|
+
if ((mib[3] = tcgetpgrp(fd)) == -1) {
|
|
658
|
+
return NULL;
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
size = sizeof kp;
|
|
662
|
+
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {
|
|
663
|
+
return NULL;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0') {
|
|
698
667
|
return NULL;
|
|
699
668
|
}
|
|
700
669
|
|
|
701
|
-
return strdup(
|
|
670
|
+
return strdup(kp.kp_proc.p_comm);
|
|
702
671
|
}
|
|
703
672
|
|
|
704
673
|
#else
|
|
@@ -815,12 +784,12 @@ done:
|
|
|
815
784
|
* Init
|
|
816
785
|
*/
|
|
817
786
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
787
|
+
Napi::Object init(Napi::Env env, Napi::Object exports) {
|
|
788
|
+
exports.Set("fork", Napi::Function::New(env, PtyFork));
|
|
789
|
+
exports.Set("open", Napi::Function::New(env, PtyOpen));
|
|
790
|
+
exports.Set("resize", Napi::Function::New(env, PtyResize));
|
|
791
|
+
exports.Set("process", Napi::Function::New(env, PtyGetProc));
|
|
792
|
+
return exports;
|
|
824
793
|
}
|
|
825
794
|
|
|
826
|
-
|
|
795
|
+
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init)
|