node-pty 1.1.0-beta1 → 1.1.0-beta10
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 +245 -289
- package/src/unixTerminal.test.ts +35 -0
- package/src/unixTerminal.ts +2 -1
- package/src/win/conpty.cc +169 -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,107 @@ 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
|
+
assert(status == napi_ok);
|
|
193
|
+
|
|
194
|
+
tsfn.Release();
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
114
198
|
/**
|
|
115
199
|
* Methods
|
|
116
200
|
*/
|
|
117
201
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
202
|
+
Napi::Value PtyFork(const Napi::CallbackInfo& info);
|
|
203
|
+
Napi::Value PtyOpen(const Napi::CallbackInfo& info);
|
|
204
|
+
Napi::Value PtyResize(const Napi::CallbackInfo& info);
|
|
205
|
+
Napi::Value PtyGetProc(const Napi::CallbackInfo& info);
|
|
122
206
|
|
|
123
207
|
/**
|
|
124
208
|
* Functions
|
|
@@ -135,15 +219,6 @@ static char *
|
|
|
135
219
|
pty_getproc(int, char *);
|
|
136
220
|
#endif
|
|
137
221
|
|
|
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
222
|
#if defined(__APPLE__) || defined(__OpenBSD__)
|
|
148
223
|
static void
|
|
149
224
|
pty_posix_spawn(char** argv, char** env,
|
|
@@ -154,62 +229,75 @@ pty_posix_spawn(char** argv, char** env,
|
|
|
154
229
|
int* err);
|
|
155
230
|
#endif
|
|
156
231
|
|
|
157
|
-
|
|
158
|
-
|
|
232
|
+
struct DelBuf {
|
|
233
|
+
int len;
|
|
234
|
+
DelBuf(int len) : len(len) {}
|
|
235
|
+
void operator()(char **p) {
|
|
236
|
+
if (p == nullptr)
|
|
237
|
+
return;
|
|
238
|
+
for (int i = 0; i < len; i++)
|
|
239
|
+
free(p[i]);
|
|
240
|
+
delete[] p;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
Napi::Value PtyFork(const Napi::CallbackInfo& info) {
|
|
245
|
+
Napi::Env napiEnv(info.Env());
|
|
246
|
+
Napi::HandleScope scope(napiEnv);
|
|
159
247
|
|
|
160
248
|
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)");
|
|
249
|
+
!info[0].IsString() ||
|
|
250
|
+
!info[1].IsArray() ||
|
|
251
|
+
!info[2].IsArray() ||
|
|
252
|
+
!info[3].IsString() ||
|
|
253
|
+
!info[4].IsNumber() ||
|
|
254
|
+
!info[5].IsNumber() ||
|
|
255
|
+
!info[6].IsNumber() ||
|
|
256
|
+
!info[7].IsNumber() ||
|
|
257
|
+
!info[8].IsBoolean() ||
|
|
258
|
+
!info[9].IsString() ||
|
|
259
|
+
!info[10].IsFunction()) {
|
|
260
|
+
throw Napi::Error::New(napiEnv, "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, helperPath, onexit)");
|
|
174
261
|
}
|
|
175
262
|
|
|
176
263
|
// file
|
|
177
|
-
|
|
264
|
+
std::string file = info[0].As<Napi::String>();
|
|
178
265
|
|
|
179
266
|
// args
|
|
180
|
-
|
|
267
|
+
Napi::Array argv_ = info[1].As<Napi::Array>();
|
|
181
268
|
|
|
182
269
|
// env
|
|
183
|
-
|
|
184
|
-
int envc = env_
|
|
185
|
-
char
|
|
270
|
+
Napi::Array env_ = info[2].As<Napi::Array>();
|
|
271
|
+
int envc = env_.Length();
|
|
272
|
+
std::unique_ptr<char *, DelBuf> env_unique_ptr(new char *[envc + 1], DelBuf(envc + 1));
|
|
273
|
+
char **env = env_unique_ptr.get();
|
|
186
274
|
env[envc] = NULL;
|
|
187
275
|
for (int i = 0; i < envc; i++) {
|
|
188
|
-
|
|
189
|
-
env[i] = strdup(
|
|
276
|
+
std::string pair = env_.Get(i).As<Napi::String>();
|
|
277
|
+
env[i] = strdup(pair.c_str());
|
|
190
278
|
}
|
|
191
279
|
|
|
192
280
|
// cwd
|
|
193
|
-
|
|
281
|
+
std::string cwd_ = info[3].As<Napi::String>();
|
|
194
282
|
|
|
195
283
|
// size
|
|
196
284
|
struct winsize winp;
|
|
197
|
-
winp.ws_col = info[4]
|
|
198
|
-
winp.ws_row = info[5]
|
|
285
|
+
winp.ws_col = info[4].As<Napi::Number>().Int32Value();
|
|
286
|
+
winp.ws_row = info[5].As<Napi::Number>().Int32Value();
|
|
199
287
|
winp.ws_xpixel = 0;
|
|
200
288
|
winp.ws_ypixel = 0;
|
|
201
289
|
|
|
202
290
|
#if !defined(__APPLE__)
|
|
203
291
|
// uid / gid
|
|
204
|
-
int uid = info[6]
|
|
205
|
-
int gid = info[7]
|
|
292
|
+
int uid = info[6].As<Napi::Number>().Int32Value();
|
|
293
|
+
int gid = info[7].As<Napi::Number>().Int32Value();
|
|
206
294
|
#endif
|
|
207
295
|
|
|
208
296
|
// termios
|
|
209
297
|
struct termios t = termios();
|
|
210
298
|
struct termios *term = &t;
|
|
211
299
|
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
|
|
212
|
-
if (
|
|
300
|
+
if (info[8].As<Napi::Boolean>().Value()) {
|
|
213
301
|
#if defined(IUTF8)
|
|
214
302
|
term->c_iflag |= IUTF8;
|
|
215
303
|
#endif
|
|
@@ -244,45 +332,44 @@ NAN_METHOD(PtyFork) {
|
|
|
244
332
|
cfsetospeed(term, B38400);
|
|
245
333
|
|
|
246
334
|
// helperPath
|
|
247
|
-
|
|
335
|
+
std::string helper_path = info[9].As<Napi::String>();
|
|
248
336
|
|
|
249
337
|
pid_t pid;
|
|
250
338
|
int master;
|
|
251
339
|
#if defined(__APPLE__)
|
|
252
|
-
int argc = argv_
|
|
340
|
+
int argc = argv_.Length();
|
|
253
341
|
int argl = argc + 4;
|
|
254
|
-
char
|
|
255
|
-
argv
|
|
256
|
-
argv[
|
|
257
|
-
argv[
|
|
342
|
+
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
|
|
343
|
+
char **argv = argv_unique_ptr.get();
|
|
344
|
+
argv[0] = strdup(helper_path.c_str());
|
|
345
|
+
argv[1] = strdup(cwd_.c_str());
|
|
346
|
+
argv[2] = strdup(file.c_str());
|
|
258
347
|
argv[argl - 1] = NULL;
|
|
259
348
|
for (int i = 0; i < argc; i++) {
|
|
260
|
-
|
|
261
|
-
argv[i + 3] = strdup(
|
|
349
|
+
std::string arg = argv_.Get(i).As<Napi::String>();
|
|
350
|
+
argv[i + 3] = strdup(arg.c_str());
|
|
262
351
|
}
|
|
263
352
|
|
|
264
353
|
int err = -1;
|
|
265
354
|
pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err);
|
|
266
355
|
if (err != 0) {
|
|
267
|
-
|
|
268
|
-
goto done;
|
|
356
|
+
throw Napi::Error::New(napiEnv, "posix_spawnp failed.");
|
|
269
357
|
}
|
|
270
358
|
if (pty_nonblock(master) == -1) {
|
|
271
|
-
|
|
272
|
-
goto done;
|
|
359
|
+
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
|
|
273
360
|
}
|
|
274
361
|
#else
|
|
275
|
-
int argc = argv_
|
|
362
|
+
int argc = argv_.Length();
|
|
276
363
|
int argl = argc + 2;
|
|
277
|
-
char
|
|
278
|
-
argv
|
|
364
|
+
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
|
|
365
|
+
char** argv = argv_unique_ptr.get();
|
|
366
|
+
argv[0] = strdup(file.c_str());
|
|
279
367
|
argv[argl - 1] = NULL;
|
|
280
368
|
for (int i = 0; i < argc; i++) {
|
|
281
|
-
|
|
282
|
-
argv[i + 1] = strdup(
|
|
369
|
+
std::string arg = argv_.Get(i).As<Napi::String>();
|
|
370
|
+
argv[i + 1] = strdup(arg.c_str());
|
|
283
371
|
}
|
|
284
372
|
|
|
285
|
-
char* cwd = strdup(*cwd_);
|
|
286
373
|
sigset_t newmask, oldmask;
|
|
287
374
|
struct sigaction sig_action;
|
|
288
375
|
// temporarily block all signals
|
|
@@ -302,14 +389,6 @@ NAN_METHOD(PtyFork) {
|
|
|
302
389
|
for (int i = 0 ; i < NSIG ; i++) { // NSIG is a macro for all signals + 1
|
|
303
390
|
sigaction(i, &sig_action, NULL);
|
|
304
391
|
}
|
|
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
392
|
}
|
|
314
393
|
|
|
315
394
|
// reenable signals
|
|
@@ -317,11 +396,10 @@ NAN_METHOD(PtyFork) {
|
|
|
317
396
|
|
|
318
397
|
switch (pid) {
|
|
319
398
|
case -1:
|
|
320
|
-
|
|
321
|
-
goto done;
|
|
399
|
+
throw Napi::Error::New(napiEnv, "forkpty(3) failed.");
|
|
322
400
|
case 0:
|
|
323
|
-
if (strlen(
|
|
324
|
-
if (chdir(
|
|
401
|
+
if (strlen(cwd_.c_str())) {
|
|
402
|
+
if (chdir(cwd_.c_str()) == -1) {
|
|
325
403
|
perror("chdir(2) failed.");
|
|
326
404
|
_exit(1);
|
|
327
405
|
}
|
|
@@ -348,62 +426,36 @@ NAN_METHOD(PtyFork) {
|
|
|
348
426
|
}
|
|
349
427
|
default:
|
|
350
428
|
if (pty_nonblock(master) == -1) {
|
|
351
|
-
|
|
352
|
-
goto done;
|
|
429
|
+
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
|
|
353
430
|
}
|
|
354
431
|
}
|
|
355
432
|
#endif
|
|
356
433
|
|
|
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;
|
|
434
|
+
Napi::Object obj = Napi::Object::New(napiEnv);
|
|
435
|
+
obj.Set("fd", Napi::Number::New(napiEnv, master));
|
|
436
|
+
obj.Set("pid", Napi::Number::New(napiEnv, pid));
|
|
437
|
+
obj.Set("pty", Napi::String::New(napiEnv, ptsname(master)));
|
|
387
438
|
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
return
|
|
439
|
+
// Set up process exit callback.
|
|
440
|
+
Napi::Function cb = info[10].As<Napi::Function>();
|
|
441
|
+
SetupExitCallback(napiEnv, cb, pid);
|
|
442
|
+
return obj;
|
|
392
443
|
}
|
|
393
444
|
|
|
394
|
-
|
|
395
|
-
|
|
445
|
+
Napi::Value PtyOpen(const Napi::CallbackInfo& info) {
|
|
446
|
+
Napi::Env env(info.Env());
|
|
447
|
+
Napi::HandleScope scope(env);
|
|
396
448
|
|
|
397
449
|
if (info.Length() != 2 ||
|
|
398
|
-
!info[0]
|
|
399
|
-
!info[1]
|
|
400
|
-
|
|
450
|
+
!info[0].IsNumber() ||
|
|
451
|
+
!info[1].IsNumber()) {
|
|
452
|
+
throw Napi::Error::New(env, "Usage: pty.open(cols, rows)");
|
|
401
453
|
}
|
|
402
454
|
|
|
403
455
|
// size
|
|
404
456
|
struct winsize winp;
|
|
405
|
-
winp.ws_col = info[0]
|
|
406
|
-
winp.ws_row = info[1]
|
|
457
|
+
winp.ws_col = info[0].As<Napi::Number>().Int32Value();
|
|
458
|
+
winp.ws_row = info[1].As<Napi::Number>().Int32Value();
|
|
407
459
|
winp.ws_xpixel = 0;
|
|
408
460
|
winp.ws_ypixel = 0;
|
|
409
461
|
|
|
@@ -412,98 +464,98 @@ NAN_METHOD(PtyOpen) {
|
|
|
412
464
|
int ret = openpty(&master, &slave, nullptr, NULL, static_cast<winsize*>(&winp));
|
|
413
465
|
|
|
414
466
|
if (ret == -1) {
|
|
415
|
-
|
|
467
|
+
throw Napi::Error::New(env, "openpty(3) failed.");
|
|
416
468
|
}
|
|
417
469
|
|
|
418
470
|
if (pty_nonblock(master) == -1) {
|
|
419
|
-
|
|
471
|
+
throw Napi::Error::New(env, "Could not set master fd to nonblocking.");
|
|
420
472
|
}
|
|
421
473
|
|
|
422
474
|
if (pty_nonblock(slave) == -1) {
|
|
423
|
-
|
|
475
|
+
throw Napi::Error::New(env, "Could not set slave fd to nonblocking.");
|
|
424
476
|
}
|
|
425
477
|
|
|
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());
|
|
478
|
+
Napi::Object obj = Napi::Object::New(env);
|
|
479
|
+
obj.Set("master", Napi::Number::New(env, master));
|
|
480
|
+
obj.Set("slave", Napi::Number::New(env, slave));
|
|
481
|
+
obj.Set("pty", Napi::String::New(env, ptsname(master)));
|
|
436
482
|
|
|
437
|
-
return
|
|
483
|
+
return obj;
|
|
438
484
|
}
|
|
439
485
|
|
|
440
|
-
|
|
441
|
-
|
|
486
|
+
Napi::Value PtyResize(const Napi::CallbackInfo& info) {
|
|
487
|
+
Napi::Env env(info.Env());
|
|
488
|
+
Napi::HandleScope scope(env);
|
|
442
489
|
|
|
443
490
|
if (info.Length() != 3 ||
|
|
444
|
-
!info[0]
|
|
445
|
-
!info[1]
|
|
446
|
-
!info[2]
|
|
447
|
-
|
|
491
|
+
!info[0].IsNumber() ||
|
|
492
|
+
!info[1].IsNumber() ||
|
|
493
|
+
!info[2].IsNumber()) {
|
|
494
|
+
throw Napi::Error::New(env, "Usage: pty.resize(fd, cols, rows)");
|
|
448
495
|
}
|
|
449
496
|
|
|
450
|
-
int fd = info[0]
|
|
497
|
+
int fd = info[0].As<Napi::Number>().Int32Value();
|
|
451
498
|
|
|
452
499
|
struct winsize winp;
|
|
453
|
-
winp.ws_col = info[1]
|
|
454
|
-
winp.ws_row = info[2]
|
|
500
|
+
winp.ws_col = info[1].As<Napi::Number>().Int32Value();
|
|
501
|
+
winp.ws_row = info[2].As<Napi::Number>().Int32Value();
|
|
455
502
|
winp.ws_xpixel = 0;
|
|
456
503
|
winp.ws_ypixel = 0;
|
|
457
504
|
|
|
458
505
|
if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
|
|
459
506
|
switch (errno) {
|
|
460
|
-
case EBADF:
|
|
461
|
-
|
|
462
|
-
case
|
|
463
|
-
|
|
507
|
+
case EBADF:
|
|
508
|
+
throw Napi::Error::New(env, "ioctl(2) failed, EBADF");
|
|
509
|
+
case EFAULT:
|
|
510
|
+
throw Napi::Error::New(env, "ioctl(2) failed, EFAULT");
|
|
511
|
+
case EINVAL:
|
|
512
|
+
throw Napi::Error::New(env, "ioctl(2) failed, EINVAL");
|
|
513
|
+
case ENOTTY:
|
|
514
|
+
throw Napi::Error::New(env, "ioctl(2) failed, ENOTTY");
|
|
464
515
|
}
|
|
465
|
-
|
|
516
|
+
throw Napi::Error::New(env, "ioctl(2) failed");
|
|
466
517
|
}
|
|
467
518
|
|
|
468
|
-
return
|
|
519
|
+
return env.Undefined();
|
|
469
520
|
}
|
|
470
521
|
|
|
471
522
|
/**
|
|
472
523
|
* Foreground Process Name
|
|
473
524
|
*/
|
|
474
|
-
|
|
475
|
-
|
|
525
|
+
Napi::Value PtyGetProc(const Napi::CallbackInfo& info) {
|
|
526
|
+
Napi::Env env(info.Env());
|
|
527
|
+
Napi::HandleScope scope(env);
|
|
476
528
|
|
|
477
529
|
#if defined(__APPLE__)
|
|
478
530
|
if (info.Length() != 1 ||
|
|
479
|
-
!info[0]
|
|
480
|
-
|
|
531
|
+
!info[0].IsNumber()) {
|
|
532
|
+
throw Napi::Error::New(env, "Usage: pty.process(pid)");
|
|
481
533
|
}
|
|
482
534
|
|
|
483
|
-
int
|
|
484
|
-
char *name = pty_getproc(
|
|
535
|
+
int fd = info[0].As<Napi::Number>().Int32Value();
|
|
536
|
+
char *name = pty_getproc(fd);
|
|
485
537
|
#else
|
|
486
538
|
if (info.Length() != 2 ||
|
|
487
|
-
!info[0]
|
|
488
|
-
!info[1]
|
|
489
|
-
|
|
539
|
+
!info[0].IsNumber() ||
|
|
540
|
+
!info[1].IsString()) {
|
|
541
|
+
throw Napi::Error::New(env, "Usage: pty.process(fd, tty)");
|
|
490
542
|
}
|
|
491
543
|
|
|
492
|
-
int fd = info[0]
|
|
544
|
+
int fd = info[0].As<Napi::Number>().Int32Value();
|
|
493
545
|
|
|
494
|
-
|
|
495
|
-
char *tty = strdup(
|
|
546
|
+
std::string tty_ = info[1].As<Napi::String>();
|
|
547
|
+
char *tty = strdup(tty_.c_str());
|
|
496
548
|
char *name = pty_getproc(fd, tty);
|
|
497
549
|
free(tty);
|
|
498
550
|
#endif
|
|
499
551
|
|
|
500
552
|
if (name == NULL) {
|
|
501
|
-
return
|
|
553
|
+
return env.Undefined();
|
|
502
554
|
}
|
|
503
555
|
|
|
504
|
-
|
|
556
|
+
Napi::String name_ = Napi::String::New(env, name);
|
|
505
557
|
free(name);
|
|
506
|
-
return
|
|
558
|
+
return name_;
|
|
507
559
|
}
|
|
508
560
|
|
|
509
561
|
/**
|
|
@@ -517,114 +569,6 @@ pty_nonblock(int fd) {
|
|
|
517
569
|
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
|
|
518
570
|
}
|
|
519
571
|
|
|
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
572
|
/**
|
|
629
573
|
* pty_getproc
|
|
630
574
|
* Taken from tmux.
|
|
@@ -692,13 +636,25 @@ pty_getproc(int fd, char *tty) {
|
|
|
692
636
|
#elif defined(__APPLE__)
|
|
693
637
|
|
|
694
638
|
static char *
|
|
695
|
-
pty_getproc(int
|
|
696
|
-
|
|
697
|
-
|
|
639
|
+
pty_getproc(int fd) {
|
|
640
|
+
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
|
|
641
|
+
size_t size;
|
|
642
|
+
struct kinfo_proc kp;
|
|
643
|
+
|
|
644
|
+
if ((mib[3] = tcgetpgrp(fd)) == -1) {
|
|
645
|
+
return NULL;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
size = sizeof kp;
|
|
649
|
+
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {
|
|
650
|
+
return NULL;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0') {
|
|
698
654
|
return NULL;
|
|
699
655
|
}
|
|
700
656
|
|
|
701
|
-
return strdup(
|
|
657
|
+
return strdup(kp.kp_proc.p_comm);
|
|
702
658
|
}
|
|
703
659
|
|
|
704
660
|
#else
|
|
@@ -815,12 +771,12 @@ done:
|
|
|
815
771
|
* Init
|
|
816
772
|
*/
|
|
817
773
|
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
774
|
+
Napi::Object init(Napi::Env env, Napi::Object exports) {
|
|
775
|
+
exports.Set("fork", Napi::Function::New(env, PtyFork));
|
|
776
|
+
exports.Set("open", Napi::Function::New(env, PtyOpen));
|
|
777
|
+
exports.Set("resize", Napi::Function::New(env, PtyResize));
|
|
778
|
+
exports.Set("process", Napi::Function::New(env, PtyGetProc));
|
|
779
|
+
return exports;
|
|
824
780
|
}
|
|
825
781
|
|
|
826
|
-
|
|
782
|
+
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init)
|