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/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() != 10 ||
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]->IsFunction()) {
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
- char *cwd = strdup(*cwd_);
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[8]).FromJust()) {
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
- // uid / gid
229
- int uid = info[6]->IntegerValue(Nan::GetCurrentContext()).FromJust();
230
- int gid = info[7]->IntegerValue(Nan::GetCurrentContext()).FromJust();
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
- struct sigaction sig_action;
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
- pid_t pid = pty_forkpty(&master, nullptr, term, &winp);
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
- if (!pid) {
248
- // remove all signal handler from child
249
- sig_action.sa_handler = SIG_DFL;
250
- sig_action.sa_flags = 0;
251
- sigemptyset(&sig_action.sa_mask);
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
- if (pid) {
260
- for (i = 0; i < argl; i++) free(argv[i]);
261
- delete[] argv;
262
- for (i = 0; i < envc; i++) free(env[i]);
263
- delete[] env;
264
- free(cwd);
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
- switch (pid) {
268
- case -1:
269
- return Nan::ThrowError("forkpty(3) failed.");
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
- if (uid != -1 && gid != -1) {
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
- pty_execvpe(argv[0], argv, env);
294
+ // reenable signals
295
+ pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
290
296
 
291
- perror("execvp(3) failed.");
292
- _exit(1);
293
- default:
294
- if (pty_nonblock(master) == -1) {
295
- return Nan::ThrowError("Could not set master fd to nonblocking.");
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
- v8::Local<v8::Object> obj = Nan::New<v8::Object>();
299
- Nan::Set(obj,
300
- Nan::New<v8::String>("fd").ToLocalChecked(),
301
- Nan::New<v8::Number>(master));
302
- Nan::Set(obj,
303
- Nan::New<v8::String>("pid").ToLocalChecked(),
304
- Nan::New<v8::Number>(pid));
305
- Nan::Set(obj,
306
- Nan::New<v8::String>("pty").ToLocalChecked(),
307
- Nan::New<v8::String>(ptsname(master)).ToLocalChecked());
308
-
309
- pty_baton *baton = new pty_baton();
310
- baton->exit_code = 0;
311
- baton->signal_code = 0;
312
- baton->cb.Reset(v8::Local<v8::Function>::Cast(info[9]));
313
- baton->pid = pid;
314
- baton->async.data = baton;
315
-
316
- uv_async_init(uv_default_loop(), &baton->async, pty_after_waitpid);
317
-
318
- uv_thread_create(&baton->tid, pty_waitpid, static_cast<void*>(baton));
319
-
320
- return info.GetReturnValue().Set(obj);
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
- return info.GetReturnValue().SetUndefined();
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
+ }
@@ -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((<any>term)._pty), '"' + (<any>term)._pty + '" should match ' + regExp.toString());
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
  }
@@ -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 || -1;
63
- const gid = opt.gid || -1;
64
- const env = assign({}, opt.env);
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
  */