node-pty 0.11.0-beta2 → 0.11.0-beta20
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 -1
- package/binding.gyp +66 -29
- package/lib/terminal.js +3 -0
- package/lib/terminal.js.map +1 -1
- package/lib/unixTerminal.js +23 -3
- package/lib/unixTerminal.js.map +1 -1
- package/lib/unixTerminal.test.js +46 -1
- package/lib/unixTerminal.test.js.map +1 -1
- package/lib/windowsConoutConnection.js +2 -1
- package/lib/windowsConoutConnection.js.map +1 -1
- package/package.json +7 -4
- package/scripts/post-install.js +1 -0
- package/src/interfaces.ts +3 -4
- package/src/native.d.ts +1 -1
- package/src/terminal.ts +3 -0
- package/src/unix/comms.h +5 -0
- package/src/unix/pty.cc +148 -175
- package/src/unix/spawn-helper.cc +70 -0
- package/src/unixTerminal.test.ts +43 -1
- package/src/unixTerminal.ts +17 -4
- package/src/win/conpty.cc +34 -22
- package/src/win/path_util.cc +1 -1
- package/src/windowsConoutConnection.ts +2 -1
- package/typings/node-pty.d.ts +10 -4
package/src/unix/pty.cc
CHANGED
|
@@ -29,6 +29,9 @@
|
|
|
29
29
|
#include <sys/wait.h>
|
|
30
30
|
#include <fcntl.h>
|
|
31
31
|
#include <signal.h>
|
|
32
|
+
#include <spawn.h>
|
|
33
|
+
|
|
34
|
+
#include "comms.h"
|
|
32
35
|
|
|
33
36
|
/* forkpty */
|
|
34
37
|
/* http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html */
|
|
@@ -54,15 +57,6 @@
|
|
|
54
57
|
#define VDISCARD VDISCRD
|
|
55
58
|
#endif
|
|
56
59
|
|
|
57
|
-
/* environ for execvpe */
|
|
58
|
-
/* node/src/node_child_process.cc */
|
|
59
|
-
#if defined(__APPLE__) && !TARGET_OS_IPHONE
|
|
60
|
-
#include <crt_externs.h>
|
|
61
|
-
#define environ (*_NSGetEnviron())
|
|
62
|
-
#else
|
|
63
|
-
extern char **environ;
|
|
64
|
-
#endif
|
|
65
|
-
|
|
66
60
|
/* for pty_getproc */
|
|
67
61
|
#if defined(__linux__)
|
|
68
62
|
#include <stdio.h>
|
|
@@ -77,6 +71,17 @@ extern char **environ;
|
|
|
77
71
|
#define NSIG 32
|
|
78
72
|
#endif
|
|
79
73
|
|
|
74
|
+
#ifdef POSIX_SPAWN_CLOEXEC_DEFAULT
|
|
75
|
+
#define HAVE_POSIX_SPAWN_CLOEXEC_DEFAULT 1
|
|
76
|
+
#else
|
|
77
|
+
#define HAVE_POSIX_SPAWN_CLOEXEC_DEFAULT 0
|
|
78
|
+
#define POSIX_SPAWN_CLOEXEC_DEFAULT 0
|
|
79
|
+
#endif
|
|
80
|
+
|
|
81
|
+
#ifndef POSIX_SPAWN_USEVFORK
|
|
82
|
+
#define POSIX_SPAWN_USEVFORK 0
|
|
83
|
+
#endif
|
|
84
|
+
|
|
80
85
|
/**
|
|
81
86
|
* Structs
|
|
82
87
|
*/
|
|
@@ -103,9 +108,6 @@ NAN_METHOD(PtyGetProc);
|
|
|
103
108
|
* Functions
|
|
104
109
|
*/
|
|
105
110
|
|
|
106
|
-
static int
|
|
107
|
-
pty_execvpe(const char *, char **, char **);
|
|
108
|
-
|
|
109
111
|
static int
|
|
110
112
|
pty_nonblock(int);
|
|
111
113
|
|
|
@@ -117,11 +119,6 @@ pty_openpty(int *, int *, char *,
|
|
|
117
119
|
const struct termios *,
|
|
118
120
|
const struct winsize *);
|
|
119
121
|
|
|
120
|
-
static pid_t
|
|
121
|
-
pty_forkpty(int *, char *,
|
|
122
|
-
const struct termios *,
|
|
123
|
-
const struct winsize *);
|
|
124
|
-
|
|
125
122
|
static void
|
|
126
123
|
pty_waitpid(void *);
|
|
127
124
|
|
|
@@ -131,10 +128,16 @@ pty_after_waitpid(uv_async_t *);
|
|
|
131
128
|
static void
|
|
132
129
|
pty_after_close(uv_handle_t *);
|
|
133
130
|
|
|
131
|
+
static void throw_for_errno(const char* message, int _errno) {
|
|
132
|
+
Nan::ThrowError((
|
|
133
|
+
message + std::string(strerror(_errno))
|
|
134
|
+
).c_str());
|
|
135
|
+
}
|
|
136
|
+
|
|
134
137
|
NAN_METHOD(PtyFork) {
|
|
135
138
|
Nan::HandleScope scope;
|
|
136
139
|
|
|
137
|
-
if (info.Length() !=
|
|
140
|
+
if (info.Length() != 12 ||
|
|
138
141
|
!info[0]->IsString() ||
|
|
139
142
|
!info[1]->IsArray() ||
|
|
140
143
|
!info[2]->IsArray() ||
|
|
@@ -144,41 +147,54 @@ NAN_METHOD(PtyFork) {
|
|
|
144
147
|
!info[6]->IsNumber() ||
|
|
145
148
|
!info[7]->IsNumber() ||
|
|
146
149
|
!info[8]->IsBoolean() ||
|
|
147
|
-
!info[9]->
|
|
150
|
+
!info[9]->IsBoolean() ||
|
|
151
|
+
!info[10]->IsFunction() ||
|
|
152
|
+
!info[11]->IsString()) {
|
|
148
153
|
return Nan::ThrowError(
|
|
149
|
-
"Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, onexit)");
|
|
154
|
+
"Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, closeFDs, utf8, onexit, helperPath)");
|
|
150
155
|
}
|
|
151
156
|
|
|
152
157
|
// file
|
|
153
158
|
Nan::Utf8String file(info[0]);
|
|
154
159
|
|
|
155
|
-
// args
|
|
156
|
-
int i = 0;
|
|
157
|
-
v8::Local<v8::Array> argv_ = v8::Local<v8::Array>::Cast(info[1]);
|
|
158
|
-
int argc = argv_->Length();
|
|
159
|
-
int argl = argc + 1 + 1;
|
|
160
|
-
char **argv = new char*[argl];
|
|
161
|
-
argv[0] = strdup(*file);
|
|
162
|
-
argv[argl-1] = NULL;
|
|
163
|
-
for (; i < argc; i++) {
|
|
164
|
-
Nan::Utf8String arg(Nan::Get(argv_, i).ToLocalChecked());
|
|
165
|
-
argv[i+1] = strdup(*arg);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
160
|
// env
|
|
169
|
-
i = 0;
|
|
170
161
|
v8::Local<v8::Array> env_ = v8::Local<v8::Array>::Cast(info[2]);
|
|
171
162
|
int envc = env_->Length();
|
|
172
163
|
char **env = new char*[envc+1];
|
|
173
164
|
env[envc] = NULL;
|
|
174
|
-
for (; i < envc; i++) {
|
|
165
|
+
for (int i = 0; i < envc; i++) {
|
|
175
166
|
Nan::Utf8String pair(Nan::Get(env_, i).ToLocalChecked());
|
|
176
167
|
env[i] = strdup(*pair);
|
|
177
168
|
}
|
|
178
169
|
|
|
179
170
|
// cwd
|
|
180
171
|
Nan::Utf8String cwd_(info[3]);
|
|
181
|
-
|
|
172
|
+
|
|
173
|
+
// uid / gid
|
|
174
|
+
int uid = info[6]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
|
175
|
+
int gid = info[7]->IntegerValue(Nan::GetCurrentContext()).FromJust();
|
|
176
|
+
|
|
177
|
+
// closeFDs
|
|
178
|
+
bool closeFDs = Nan::To<bool>(info[8]).FromJust();
|
|
179
|
+
bool explicitlyCloseFDs = closeFDs && !HAVE_POSIX_SPAWN_CLOEXEC_DEFAULT;
|
|
180
|
+
|
|
181
|
+
// args
|
|
182
|
+
v8::Local<v8::Array> argv_ = v8::Local<v8::Array>::Cast(info[1]);
|
|
183
|
+
|
|
184
|
+
const int EXTRA_ARGS = 5;
|
|
185
|
+
int argc = argv_->Length();
|
|
186
|
+
int argl = argc + EXTRA_ARGS + 1;
|
|
187
|
+
char **argv = new char*[argl];
|
|
188
|
+
argv[0] = strdup(*cwd_);
|
|
189
|
+
argv[1] = strdup(std::to_string(uid).c_str());
|
|
190
|
+
argv[2] = strdup(std::to_string(gid).c_str());
|
|
191
|
+
argv[3] = strdup(explicitlyCloseFDs ? "1": "0");
|
|
192
|
+
argv[4] = strdup(*file);
|
|
193
|
+
argv[argl - 1] = NULL;
|
|
194
|
+
for (int i = 0; i < argc; i++) {
|
|
195
|
+
Nan::Utf8String arg(Nan::Get(argv_, i).ToLocalChecked());
|
|
196
|
+
argv[i + EXTRA_ARGS] = strdup(*arg);
|
|
197
|
+
}
|
|
182
198
|
|
|
183
199
|
// size
|
|
184
200
|
struct winsize winp;
|
|
@@ -191,7 +207,7 @@ NAN_METHOD(PtyFork) {
|
|
|
191
207
|
struct termios t = termios();
|
|
192
208
|
struct termios *term = &t;
|
|
193
209
|
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
|
|
194
|
-
if (Nan::To<bool>(info[
|
|
210
|
+
if (Nan::To<bool>(info[9]).FromJust()) {
|
|
195
211
|
#if defined(IUTF8)
|
|
196
212
|
term->c_iflag |= IUTF8;
|
|
197
213
|
#endif
|
|
@@ -225,15 +241,12 @@ NAN_METHOD(PtyFork) {
|
|
|
225
241
|
cfsetispeed(term, B38400);
|
|
226
242
|
cfsetospeed(term, B38400);
|
|
227
243
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
// fork the pty
|
|
233
|
-
int master = -1;
|
|
244
|
+
// helperPath
|
|
245
|
+
Nan::Utf8String helper_path_(info[11]);
|
|
246
|
+
char *helper_path = strdup(*helper_path_);
|
|
234
247
|
|
|
235
248
|
sigset_t newmask, oldmask;
|
|
236
|
-
|
|
249
|
+
int flags = POSIX_SPAWN_USEVFORK;
|
|
237
250
|
|
|
238
251
|
// temporarily block all signals
|
|
239
252
|
// this is needed due to a race condition in openpty
|
|
@@ -242,85 +255,109 @@ NAN_METHOD(PtyFork) {
|
|
|
242
255
|
sigfillset(&newmask);
|
|
243
256
|
pthread_sigmask(SIG_SETMASK, &newmask, &oldmask);
|
|
244
257
|
|
|
245
|
-
|
|
258
|
+
int master, slave;
|
|
259
|
+
int ret = pty_openpty(&master, &slave, nullptr, term, &winp);
|
|
260
|
+
if (ret == -1) {
|
|
261
|
+
perror("openpty failed");
|
|
262
|
+
Nan::ThrowError("openpty failed.");
|
|
263
|
+
goto done;
|
|
264
|
+
}
|
|
246
265
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
for (int i = 0 ; i < NSIG ; i++) { // NSIG is a macro for all signals + 1
|
|
253
|
-
sigaction(i, &sig_action, NULL);
|
|
254
|
-
}
|
|
266
|
+
int comms_pipe[2];
|
|
267
|
+
if (pipe(comms_pipe)) {
|
|
268
|
+
perror("pipe() failed");
|
|
269
|
+
Nan::ThrowError("pipe() failed.");
|
|
270
|
+
goto done;
|
|
255
271
|
}
|
|
256
|
-
// reenable signals
|
|
257
|
-
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
|
|
258
272
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
273
|
+
posix_spawn_file_actions_t acts;
|
|
274
|
+
posix_spawn_file_actions_init(&acts);
|
|
275
|
+
posix_spawn_file_actions_adddup2(&acts, slave, STDIN_FILENO);
|
|
276
|
+
posix_spawn_file_actions_adddup2(&acts, slave, STDOUT_FILENO);
|
|
277
|
+
posix_spawn_file_actions_adddup2(&acts, slave, STDERR_FILENO);
|
|
278
|
+
posix_spawn_file_actions_adddup2(&acts, comms_pipe[1], COMM_PIPE_FD);
|
|
279
|
+
posix_spawn_file_actions_addclose(&acts, comms_pipe[1]);
|
|
280
|
+
|
|
281
|
+
posix_spawnattr_t attrs;
|
|
282
|
+
posix_spawnattr_init(&attrs);
|
|
283
|
+
if (closeFDs) {
|
|
284
|
+
flags |= POSIX_SPAWN_CLOEXEC_DEFAULT;
|
|
265
285
|
}
|
|
286
|
+
posix_spawnattr_setflags(&attrs, flags);
|
|
266
287
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
case 0:
|
|
271
|
-
if (strlen(cwd)) {
|
|
272
|
-
if (chdir(cwd) == -1) {
|
|
273
|
-
perror("chdir(2) failed.");
|
|
274
|
-
_exit(1);
|
|
275
|
-
}
|
|
276
|
-
}
|
|
288
|
+
{ // suppresses "jump bypasses variable initialization" errors
|
|
289
|
+
pid_t pid;
|
|
290
|
+
auto error = posix_spawn(&pid, helper_path, &acts, &attrs, argv, env);
|
|
277
291
|
|
|
278
|
-
|
|
279
|
-
if (setgid(gid) == -1) {
|
|
280
|
-
perror("setgid(2) failed.");
|
|
281
|
-
_exit(1);
|
|
282
|
-
}
|
|
283
|
-
if (setuid(uid) == -1) {
|
|
284
|
-
perror("setuid(2) failed.");
|
|
285
|
-
_exit(1);
|
|
286
|
-
}
|
|
287
|
-
}
|
|
292
|
+
close(comms_pipe[1]);
|
|
288
293
|
|
|
289
|
-
|
|
294
|
+
// reenable signals
|
|
295
|
+
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
|
|
290
296
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
297
|
+
if (error) {
|
|
298
|
+
throw_for_errno("posix_spawn failed: ", error);
|
|
299
|
+
goto done;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
int helper_error[2];
|
|
303
|
+
auto bytes_read = read(comms_pipe[0], &helper_error, sizeof(helper_error));
|
|
304
|
+
close(comms_pipe[0]);
|
|
305
|
+
|
|
306
|
+
if (bytes_read == sizeof(helper_error)) {
|
|
307
|
+
if (helper_error[0] == COMM_ERR_EXEC) {
|
|
308
|
+
throw_for_errno("exec() failed: ", helper_error[1]);
|
|
309
|
+
} else if (helper_error[0] == COMM_ERR_CHDIR) {
|
|
310
|
+
throw_for_errno("chdir() failed: ", helper_error[1]);
|
|
311
|
+
} else if (helper_error[0] == COMM_ERR_SETUID) {
|
|
312
|
+
throw_for_errno("setuid() failed: ", helper_error[1]);
|
|
313
|
+
} else if (helper_error[0] == COMM_ERR_SETGID) {
|
|
314
|
+
throw_for_errno("setgid() failed: ", helper_error[1]);
|
|
296
315
|
}
|
|
316
|
+
goto done;
|
|
317
|
+
}
|
|
297
318
|
|
|
298
|
-
|
|
299
|
-
Nan::
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
Nan::
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
319
|
+
if (pty_nonblock(master) == -1) {
|
|
320
|
+
Nan::ThrowError("Could not set master fd to nonblocking.");
|
|
321
|
+
goto done;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
v8::Local<v8::Object> obj = Nan::New<v8::Object>();
|
|
325
|
+
Nan::Set(obj,
|
|
326
|
+
Nan::New<v8::String>("fd").ToLocalChecked(),
|
|
327
|
+
Nan::New<v8::Number>(master));
|
|
328
|
+
Nan::Set(obj,
|
|
329
|
+
Nan::New<v8::String>("pid").ToLocalChecked(),
|
|
330
|
+
Nan::New<v8::Number>(pid));
|
|
331
|
+
Nan::Set(obj,
|
|
332
|
+
Nan::New<v8::String>("pty").ToLocalChecked(),
|
|
333
|
+
Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
|
|
334
|
+
|
|
335
|
+
pty_baton *baton = new pty_baton();
|
|
336
|
+
baton->exit_code = 0;
|
|
337
|
+
baton->signal_code = 0;
|
|
338
|
+
baton->cb.Reset(v8::Local<v8::Function>::Cast(info[10]));
|
|
339
|
+
baton->pid = pid;
|
|
340
|
+
baton->async.data = baton;
|
|
341
|
+
|
|
342
|
+
uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid);
|
|
343
|
+
|
|
344
|
+
uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton));
|
|
345
|
+
|
|
346
|
+
info.GetReturnValue().Set(obj);
|
|
321
347
|
}
|
|
348
|
+
done:
|
|
349
|
+
posix_spawn_file_actions_destroy(&acts);
|
|
350
|
+
posix_spawnattr_destroy(&attrs);
|
|
322
351
|
|
|
323
|
-
|
|
352
|
+
if (argv) {
|
|
353
|
+
for (int i = 0; i < argl; i++) free(argv[i]);
|
|
354
|
+
delete[] argv;
|
|
355
|
+
}
|
|
356
|
+
if (env) {
|
|
357
|
+
for (int i = 0; i < envc; i++) free(env[i]);
|
|
358
|
+
delete[] env;
|
|
359
|
+
}
|
|
360
|
+
free(helper_path);
|
|
324
361
|
}
|
|
325
362
|
|
|
326
363
|
NAN_METHOD(PtyOpen) {
|
|
@@ -428,21 +465,6 @@ NAN_METHOD(PtyGetProc) {
|
|
|
428
465
|
return info.GetReturnValue().Set(name_);
|
|
429
466
|
}
|
|
430
467
|
|
|
431
|
-
/**
|
|
432
|
-
* execvpe
|
|
433
|
-
*/
|
|
434
|
-
|
|
435
|
-
// execvpe(3) is not portable.
|
|
436
|
-
// http://www.gnu.org/software/gnulib/manual/html_node/execvpe.html
|
|
437
|
-
static int
|
|
438
|
-
pty_execvpe(const char *file, char **argv, char **envp) {
|
|
439
|
-
char **old = environ;
|
|
440
|
-
environ = envp;
|
|
441
|
-
int ret = execvp(file, argv);
|
|
442
|
-
environ = old;
|
|
443
|
-
return ret;
|
|
444
|
-
}
|
|
445
|
-
|
|
446
468
|
/**
|
|
447
469
|
* Nonblocking FD
|
|
448
470
|
*/
|
|
@@ -670,55 +692,6 @@ err:
|
|
|
670
692
|
#endif
|
|
671
693
|
}
|
|
672
694
|
|
|
673
|
-
static pid_t
|
|
674
|
-
pty_forkpty(int *amaster,
|
|
675
|
-
char *name,
|
|
676
|
-
const struct termios *termp,
|
|
677
|
-
const struct winsize *winp) {
|
|
678
|
-
#if defined(__sun)
|
|
679
|
-
int master, slave;
|
|
680
|
-
|
|
681
|
-
int ret = pty_openpty(&master, &slave, name, termp, winp);
|
|
682
|
-
if (ret == -1) return -1;
|
|
683
|
-
if (amaster) *amaster = master;
|
|
684
|
-
|
|
685
|
-
pid_t pid = fork();
|
|
686
|
-
|
|
687
|
-
switch (pid) {
|
|
688
|
-
case -1: // error in fork, we are still in parent
|
|
689
|
-
close(master);
|
|
690
|
-
close(slave);
|
|
691
|
-
return -1;
|
|
692
|
-
case 0: // we are in the child process
|
|
693
|
-
close(master);
|
|
694
|
-
setsid();
|
|
695
|
-
|
|
696
|
-
#if defined(TIOCSCTTY)
|
|
697
|
-
// glibc does this
|
|
698
|
-
if (ioctl(slave, TIOCSCTTY, NULL) == -1) {
|
|
699
|
-
_exit(1);
|
|
700
|
-
}
|
|
701
|
-
#endif
|
|
702
|
-
|
|
703
|
-
dup2(slave, 0);
|
|
704
|
-
dup2(slave, 1);
|
|
705
|
-
dup2(slave, 2);
|
|
706
|
-
|
|
707
|
-
if (slave > 2) close(slave);
|
|
708
|
-
|
|
709
|
-
return 0;
|
|
710
|
-
default: // we are in the parent process
|
|
711
|
-
close(slave);
|
|
712
|
-
return pid;
|
|
713
|
-
}
|
|
714
|
-
|
|
715
|
-
return -1;
|
|
716
|
-
#else
|
|
717
|
-
return forkpty(amaster, name, (termios *)termp, (winsize *)winp);
|
|
718
|
-
#endif
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
|
|
722
695
|
/**
|
|
723
696
|
* Init
|
|
724
697
|
*/
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#include <errno.h>
|
|
2
|
+
#include <fcntl.h>
|
|
3
|
+
#include <signal.h>
|
|
4
|
+
#include <string>
|
|
5
|
+
#include <sys/ioctl.h>
|
|
6
|
+
#include <sys/resource.h>
|
|
7
|
+
#include <termios.h>
|
|
8
|
+
#include <unistd.h>
|
|
9
|
+
#include <string.h>
|
|
10
|
+
|
|
11
|
+
#include "comms.h"
|
|
12
|
+
|
|
13
|
+
void bail (int type, int code) {
|
|
14
|
+
int buf[2] = { type, code };
|
|
15
|
+
(void)! write(COMM_PIPE_FD, &buf, sizeof(buf));
|
|
16
|
+
_exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
int main (int argc, char** argv) {
|
|
20
|
+
sigset_t empty_set;
|
|
21
|
+
sigemptyset(&empty_set);
|
|
22
|
+
pthread_sigmask(SIG_SETMASK, &empty_set, nullptr);
|
|
23
|
+
|
|
24
|
+
setsid();
|
|
25
|
+
|
|
26
|
+
#if defined(TIOCSCTTY)
|
|
27
|
+
// glibc does this
|
|
28
|
+
if (ioctl(STDIN_FILENO, TIOCSCTTY, NULL) == -1) {
|
|
29
|
+
_exit(1);
|
|
30
|
+
}
|
|
31
|
+
#else
|
|
32
|
+
char *slave_path = ttyname(STDIN_FILENO);
|
|
33
|
+
// open implicit attaches a process to a terminal device if:
|
|
34
|
+
// - process has no controlling terminal yet
|
|
35
|
+
// - O_NOCTTY is not set
|
|
36
|
+
close(open(slave_path, O_RDWR));
|
|
37
|
+
#endif
|
|
38
|
+
|
|
39
|
+
char *cwd = argv[0];
|
|
40
|
+
int uid = std::stoi(argv[1]);
|
|
41
|
+
int gid = std::stoi(argv[2]);
|
|
42
|
+
bool closeFDs = std::stoi(argv[3]);
|
|
43
|
+
char *file = argv[4];
|
|
44
|
+
argv = &argv[4];
|
|
45
|
+
|
|
46
|
+
fcntl(COMM_PIPE_FD, F_SETFD, FD_CLOEXEC);
|
|
47
|
+
|
|
48
|
+
if (strlen(cwd) && chdir(cwd) == -1) {
|
|
49
|
+
bail(COMM_ERR_CHDIR, errno);
|
|
50
|
+
}
|
|
51
|
+
if (uid != -1 && setuid(uid) == -1) {
|
|
52
|
+
bail(COMM_ERR_SETUID, errno);
|
|
53
|
+
}
|
|
54
|
+
if (gid != -1 && setgid(gid) == -1) {
|
|
55
|
+
bail(COMM_ERR_SETGID, errno);
|
|
56
|
+
}
|
|
57
|
+
if (closeFDs) {
|
|
58
|
+
struct rlimit rlim_ofile;
|
|
59
|
+
getrlimit(RLIMIT_NOFILE, &rlim_ofile);
|
|
60
|
+
for (rlim_t fd = STDERR_FILENO + 1; fd < rlim_ofile.rlim_cur; fd++) {
|
|
61
|
+
if (fd != COMM_PIPE_FD) {
|
|
62
|
+
close(fd);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
execvp(file, argv);
|
|
68
|
+
bail(COMM_ERR_EXEC, errno);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
package/src/unixTerminal.test.ts
CHANGED
|
@@ -7,6 +7,8 @@ import { UnixTerminal } from './unixTerminal';
|
|
|
7
7
|
import * as assert from 'assert';
|
|
8
8
|
import * as cp from 'child_process';
|
|
9
9
|
import * as path from 'path';
|
|
10
|
+
import * as tty from 'tty';
|
|
11
|
+
import { constants } from 'os';
|
|
10
12
|
import { pollUntil } from './testUtils.test';
|
|
11
13
|
|
|
12
14
|
const FIXTURES_PATH = path.normalize(path.join(__dirname, '..', 'fixtures', 'utf8-character.txt'));
|
|
@@ -26,8 +28,9 @@ if (process.platform !== 'win32') {
|
|
|
26
28
|
regExp = /^\/dev\/tty[p-sP-S][a-z0-9]+$/;
|
|
27
29
|
}
|
|
28
30
|
if (regExp) {
|
|
29
|
-
assert.ok(regExp.test(
|
|
31
|
+
assert.ok(regExp.test(term.ptsName), '"' + term.ptsName + '" should match ' + regExp.toString());
|
|
30
32
|
}
|
|
33
|
+
assert.ok(tty.isatty(term.fd));
|
|
31
34
|
});
|
|
32
35
|
});
|
|
33
36
|
|
|
@@ -104,6 +107,17 @@ if (process.platform !== 'win32') {
|
|
|
104
107
|
term.master.write('master\n');
|
|
105
108
|
});
|
|
106
109
|
});
|
|
110
|
+
describe('close', () => {
|
|
111
|
+
const term = new UnixTerminal('node');
|
|
112
|
+
it('should exit when terminal is destroyed programmatically', (done) => {
|
|
113
|
+
term.on('exit', (code, signal) => {
|
|
114
|
+
assert.strictEqual(code, 0);
|
|
115
|
+
assert.strictEqual(signal, constants.signals.SIGHUP);
|
|
116
|
+
done();
|
|
117
|
+
});
|
|
118
|
+
term.destroy();
|
|
119
|
+
});
|
|
120
|
+
});
|
|
107
121
|
describe('signals in parent and child', () => {
|
|
108
122
|
it('SIGINT - custom in parent and child', done => {
|
|
109
123
|
// this test is cumbersome - we have to run it in a sub process to
|
|
@@ -239,5 +253,33 @@ if (process.platform !== 'win32') {
|
|
|
239
253
|
});
|
|
240
254
|
});
|
|
241
255
|
});
|
|
256
|
+
describe('spawn', () => {
|
|
257
|
+
it('should handle exec() errors', (done) => {
|
|
258
|
+
try {
|
|
259
|
+
new UnixTerminal('/bin/bogus.exe', []);
|
|
260
|
+
done(new Error('should have failed'));
|
|
261
|
+
} catch {
|
|
262
|
+
done();
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
it('should handle chdir() errors', (done) => {
|
|
266
|
+
try {
|
|
267
|
+
new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' });
|
|
268
|
+
done(new Error('should have failed'));
|
|
269
|
+
} catch (e) {
|
|
270
|
+
assert.equal(e.toString(), 'Error: chdir() failed: No such file or directory');
|
|
271
|
+
done();
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
it('should handle setuid() errors', (done) => {
|
|
275
|
+
try {
|
|
276
|
+
new UnixTerminal('/bin/echo', [], { uid: 999999 });
|
|
277
|
+
done(new Error('should have failed'));
|
|
278
|
+
} catch (e) {
|
|
279
|
+
assert.equal(e.toString(), 'Error: setuid() failed: Operation not permitted');
|
|
280
|
+
done();
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
242
284
|
});
|
|
243
285
|
}
|
package/src/unixTerminal.ts
CHANGED
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
* Copyright (c) 2018, Microsoft Corporation (MIT License).
|
|
5
5
|
*/
|
|
6
6
|
import * as net from 'net';
|
|
7
|
+
import * as path from 'path';
|
|
7
8
|
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
|
|
8
9
|
import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';
|
|
9
10
|
import { ArgvOrCommandLine } from './types';
|
|
10
11
|
import { assign } from './utils';
|
|
11
12
|
|
|
12
13
|
let pty: IUnixNative;
|
|
14
|
+
let helperPath: string;
|
|
13
15
|
try {
|
|
14
16
|
pty = require('../build/Release/pty.node');
|
|
17
|
+
helperPath = '../build/Release/spawn-helper';
|
|
15
18
|
} catch (outerError) {
|
|
16
19
|
try {
|
|
17
20
|
pty = require('../build/Debug/pty.node');
|
|
21
|
+
helperPath = '../build/Debug/spawn-helper';
|
|
18
22
|
} catch (innerError) {
|
|
19
23
|
console.error('innerError', innerError);
|
|
20
24
|
// Re-throw the exception from the Release require if the Debug require fails as well
|
|
@@ -22,6 +26,10 @@ try {
|
|
|
22
26
|
}
|
|
23
27
|
}
|
|
24
28
|
|
|
29
|
+
helperPath = path.resolve(__dirname, helperPath);
|
|
30
|
+
helperPath = helperPath.replace('app.asar', 'app.asar.unpacked');
|
|
31
|
+
helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked');
|
|
32
|
+
|
|
25
33
|
const DEFAULT_FILE = 'sh';
|
|
26
34
|
const DEFAULT_NAME = 'xterm';
|
|
27
35
|
const DESTROY_SOCKET_TIMEOUT_MS = 200;
|
|
@@ -59,9 +67,10 @@ export class UnixTerminal extends Terminal {
|
|
|
59
67
|
|
|
60
68
|
this._cols = opt.cols || DEFAULT_COLS;
|
|
61
69
|
this._rows = opt.rows || DEFAULT_ROWS;
|
|
62
|
-
const uid = opt.uid
|
|
63
|
-
const gid = opt.gid
|
|
64
|
-
const
|
|
70
|
+
const uid = opt.uid ?? -1;
|
|
71
|
+
const gid = opt.gid ?? -1;
|
|
72
|
+
const closeFDs = opt.closeFDs || false;
|
|
73
|
+
const env: IProcessEnv = assign({}, opt.env);
|
|
65
74
|
|
|
66
75
|
if (opt.env === process.env) {
|
|
67
76
|
this._sanitizeEnv(env);
|
|
@@ -103,7 +112,7 @@ export class UnixTerminal extends Terminal {
|
|
|
103
112
|
};
|
|
104
113
|
|
|
105
114
|
// fork
|
|
106
|
-
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), onexit);
|
|
115
|
+
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), closeFDs, onexit, helperPath);
|
|
107
116
|
|
|
108
117
|
this._socket = new PipeSocket(term.fd);
|
|
109
118
|
if (encoding !== null) {
|
|
@@ -169,6 +178,10 @@ export class UnixTerminal extends Terminal {
|
|
|
169
178
|
this._socket.write(data);
|
|
170
179
|
}
|
|
171
180
|
|
|
181
|
+
/* Accessors */
|
|
182
|
+
get fd(): number { return this._fd; }
|
|
183
|
+
get ptsName(): string { return this._pty; }
|
|
184
|
+
|
|
172
185
|
/**
|
|
173
186
|
* openpty
|
|
174
187
|
*/
|