From eb8b77c990cde782f34bebcbc8a9aa1ae7617551 Mon Sep 17 00:00:00 2001 From: deepak1556 Date: Sat, 1 Apr 2023 00:07:06 +0900 Subject: [PATCH] fix: controlling terminal on macOS --- binding.gyp | 14 ++++ scripts/post-install.js | 1 + src/native.d.ts | 2 +- src/unix/pty.cc | 139 +++++++++------------------------------ src/unix/spawn-helper.cc | 23 +++++++ src/unixTerminal.test.ts | 47 +++++-------- src/unixTerminal.ts | 9 ++- 7 files changed, 94 insertions(+), 141 deletions(-) create mode 100644 src/unix/spawn-helper.cc diff --git a/binding.gyp b/binding.gyp index feecbf6d7..79a93e7f5 100644 --- a/binding.gyp +++ b/binding.gyp @@ -98,6 +98,20 @@ ] } ] + }], + ['OS=="mac"', { + 'targets': [ + { + 'target_name': 'spawn-helper', + 'type': 'executable', + 'sources': [ + 'src/unix/spawn-helper.cc', + ], + "xcode_settings": { + "MACOSX_DEPLOYMENT_TARGET":"10.7" + } + }, + ] }] ] } diff --git a/scripts/post-install.js b/scripts/post-install.js index 2c026ee5d..745fbdbef 100644 --- a/scripts/post-install.js +++ b/scripts/post-install.js @@ -9,6 +9,7 @@ var BUILD_FILES = [ path.join(RELEASE_DIR, 'conpty_console_list.pdb'), path.join(RELEASE_DIR, 'pty.node'), path.join(RELEASE_DIR, 'pty.pdb'), + path.join(RELEASE_DIR, 'spawn-helper'), path.join(RELEASE_DIR, 'winpty-agent.exe'), path.join(RELEASE_DIR, 'winpty-agent.pdb'), path.join(RELEASE_DIR, 'winpty.dll'), diff --git a/src/native.d.ts b/src/native.d.ts index 0d2adefe6..7bf77e556 100644 --- a/src/native.d.ts +++ b/src/native.d.ts @@ -18,7 +18,7 @@ interface IWinptyNative { } interface IUnixNative { - fork(file: string, args: string[], parsedEnv: string[], cwd: string, cols: number, rows: number, uid: number, gid: number, useUtf8: boolean, onExitCallback: (code: number, signal: number) => void): IUnixProcess; + fork(file: string, args: string[], parsedEnv: string[], cwd: string, cols: number, rows: number, uid: number, gid: number, useUtf8: boolean, helperPath: string, onExitCallback: (code: number, signal: number) => void): IUnixProcess; open(cols: number, rows: number): IUnixOpenProcess; process(fd: number, pty: string): string; process(pid: number): string; diff --git a/src/unix/pty.cc b/src/unix/pty.cc index 79c5adbf5..5dc80fa00 100644 --- a/src/unix/pty.cc +++ b/src/unix/pty.cc @@ -145,7 +145,6 @@ pty_after_close(uv_handle_t *); #if defined(__APPLE__) || defined(__OpenBSD__) static void pty_posix_spawn(char** argv, char** env, - char* cwd, const struct termios *termp, const struct winsize *winp, int* master, @@ -156,7 +155,7 @@ pty_posix_spawn(char** argv, char** env, NAN_METHOD(PtyFork) { Nan::HandleScope scope; - if (info.Length() != 10 || + if (info.Length() != 11 || !info[0]->IsString() || !info[1]->IsArray() || !info[2]->IsArray() || @@ -166,9 +165,10 @@ NAN_METHOD(PtyFork) { !info[6]->IsNumber() || !info[7]->IsNumber() || !info[8]->IsBoolean() || - !info[9]->IsFunction()) { + !info[9]->IsString() || + !info[10]->IsFunction()) { return Nan::ThrowError( - "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, onexit)"); + "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, helperPath, onexit)"); } // file @@ -189,7 +189,6 @@ NAN_METHOD(PtyFork) { // cwd Nan::Utf8String cwd_(info[3]); - char* cwd = strdup(*cwd_); // size struct winsize winp; @@ -242,21 +241,26 @@ NAN_METHOD(PtyFork) { cfsetispeed(term, B38400); cfsetospeed(term, B38400); + // helperPath + Nan::Utf8String helper_path(info[9]); + + pid_t pid; + int master; +#if defined(__APPLE__) int argc = argv_->Length(); - int argl = argc + 2; + int argl = argc + 4; char **argv = new char*[argl]; - argv[0] = strdup(*file); + argv[0] = strdup(*helper_path); + argv[1] = strdup(*cwd_); + argv[2] = strdup(*file); argv[argl - 1] = NULL; for (int i = 0; i < argc; i++) { Nan::Utf8String arg(Nan::Get(argv_, i).ToLocalChecked()); - argv[i + 1] = strdup(*arg); + argv[i + 3] = strdup(*arg); } - pid_t pid; - int master; -#if defined(__APPLE__) - int err = 0; - pty_posix_spawn(argv, env, cwd, term, &winp, &master, &pid, &err); + int err = -1; + pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err); if (err != 0) { Nan::ThrowError("posix_spawnp failed."); goto done; @@ -266,6 +270,17 @@ NAN_METHOD(PtyFork) { goto done; } #else + int argc = argv_->Length(); + int argl = argc + 2; + char **argv = new char*[argl]; + argv[0] = strdup(*file); + argv[argl - 1] = NULL; + for (int i = 0; i < argc; i++) { + Nan::Utf8String arg(Nan::Get(argv_, i).ToLocalChecked()); + argv[i + 1] = strdup(*arg); + } + + char* cwd = strdup(*cwd_); sigset_t newmask, oldmask; struct sigaction sig_action; // temporarily block all signals @@ -352,7 +367,7 @@ NAN_METHOD(PtyFork) { pty_baton *baton = new pty_baton(); baton->exit_code = 0; baton->signal_code = 0; - baton->cb.Reset(v8::Local::Cast(info[9])); + baton->cb.Reset(v8::Local::Cast(info[10])); baton->pid = pid; baton->async.data = baton; @@ -370,8 +385,6 @@ NAN_METHOD(PtyFork) { for (int i = 0; i < envc; i++) free(env[i]); delete[] env; - - free(cwd); #endif return info.GetReturnValue().SetUndefined(); } @@ -698,7 +711,6 @@ pty_getproc(int fd, char *tty) { #if defined(__APPLE__) static void pty_posix_spawn(char** argv, char** env, - char* cwd, const struct termios *termp, const struct winsize *winp, int* master, @@ -706,14 +718,6 @@ pty_posix_spawn(char** argv, char** env, int* err) { int low_fds[3]; size_t count = 0; - const char *p; - const char *z; - size_t l; - size_t k; - int seen_eacces; - const char *path; - char** env_iterator; - const char path_var[] = "PATH="; for (; count < 3; count++) { low_fds[count] = posix_openpt(O_RDWR); @@ -769,16 +773,6 @@ pty_posix_spawn(char** argv, char** env, posix_spawn_file_actions_adddup2(&acts, slave, STDERR_FILENO); posix_spawn_file_actions_addclose(&acts, slave); posix_spawn_file_actions_addclose(&acts, *master); - if (strlen(cwd)) { - if (__builtin_available(macOS 10.15, *)) { - posix_spawn_file_actions_addchdir_np(&acts, cwd); - } else { - *err = pthread_chdir_np(cwd); - if (*err != 0) { - goto done; - } - } - } posix_spawnattr_t attrs; posix_spawnattr_init(&attrs); @@ -802,78 +796,9 @@ pty_posix_spawn(char** argv, char** env, goto done; } - // path resolution is copied from - // https://github.com/libuv/libuv/blob/7b84d5b0ecb737b4cc30ce63eade690d994e00a6/src/unix/process.c#L695-L764 - if (strchr(argv[0], '/') != NULL) { - do - *err = posix_spawn(pid, argv[0], &acts, &attrs, argv, env); - while (*err == EINTR); - } else { - - for (env_iterator = env; *env_iterator != NULL; env_iterator++) { - if (strncmp(*env_iterator, path_var, sizeof(path_var) - 1) == 0) { - /* Found "PATH=" at the beginning of the string */ - path = *env_iterator + sizeof(path_var) - 1; - } - } - - if (path == NULL) - path = _PATH_DEFPATH; - - k = strnlen(argv[0], NAME_MAX + 1); - if (k > NAME_MAX) - goto done; - - l = strnlen(path, PATH_MAX - 1) + 1; - - for (p = path;; p = z) { - /* Compose the new process file from the entry in the PATH - * environment variable and the actual file name */ - char b[PATH_MAX + NAME_MAX]; - z = strchr(p, ':'); - if (!z) - z = p + strlen(p); - if ((size_t)(z - p) >= l) { - if (!*z++) - break; - - continue; - } - memcpy(b, p, z - p); - b[z - p] = '/'; - memcpy(b + (z - p) + (z > p), argv[0], k + 1); - - do - *err = posix_spawn(pid, b, &acts, &attrs, argv, env); - while (*err == EINTR); - - switch (*err) { - case EACCES: - seen_eacces = 1; - break; /* continue search */ - case ENOENT: - case ENOTDIR: - break; /* continue search */ - default: - goto done; - } - - if (!*z++) - break; - } - - if (seen_eacces) - goto done; - } - - // Restore the thread's working directory if it was changed. - if (strlen(cwd)) { - if (__builtin_available(macOS 10.15, *)) { - // __builtin_available is special and cannot be negated. - } else { - pthread_fchdir_np(-1); - } - } + do + *err = posix_spawn(pid, argv[0], &acts, &attrs, argv, env); + while (*err == EINTR); done: posix_spawn_file_actions_destroy(&acts); posix_spawnattr_destroy(&attrs); diff --git a/src/unix/spawn-helper.cc b/src/unix/spawn-helper.cc new file mode 100644 index 000000000..8066328f5 --- /dev/null +++ b/src/unix/spawn-helper.cc @@ -0,0 +1,23 @@ +#include +#include +#include +#include + +int main (int argc, char** argv) { + char *slave_path = ttyname(STDIN_FILENO); + // open implicit attaches a process to a terminal device if: + // - process has no controlling terminal yet + // - O_NOCTTY is not set + close(open(slave_path, O_RDWR)); + + char *cwd = argv[1]; + char *file = argv[2]; + argv = &argv[2]; + + if (strlen(cwd) && chdir(cwd) == -1) { + _exit(1); + } + + execvp(file, argv); + return 1; +} diff --git a/src/unixTerminal.test.ts b/src/unixTerminal.test.ts index 75301ddce..dedceb7f0 100644 --- a/src/unixTerminal.test.ts +++ b/src/unixTerminal.test.ts @@ -255,38 +255,7 @@ if (process.platform !== 'win32') { }); }); describe('spawn', () => { - if (process.platform === 'linux') { - it('should handle exec() errors', (done) => { - const term = new UnixTerminal('/bin/bogus.exe', []); - term.on('exit', (code, signal) => { - assert.strictEqual(code, 1); - done(); - }); - }); - it('should handle chdir() errors', (done) => { - const term = new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' }); - term.on('exit', (code, signal) => { - assert.strictEqual(code, 1); - done(); - }); - }); - } else if (process.platform === 'darwin') { - it('should handle exec() errors', (done) => { - try { - new UnixTerminal('/bin/bogus.exe', []); - done(new Error('should have failed')); - } catch { - done(); - } - }); - it('should handle chdir() errors', (done) => { - try { - new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' }); - done(new Error('should have failed')); - } catch (e) { - done(); - } - }); + if (process.platform === 'darwin') { it('should return the name of the process', (done) => { const term = new UnixTerminal('/bin/echo'); assert.strictEqual(term.process, '/bin/echo'); @@ -331,6 +300,20 @@ if (process.platform !== 'win32') { }); }); } + it('should handle exec() errors', (done) => { + const term = new UnixTerminal('/bin/bogus.exe', []); + term.on('exit', (code, signal) => { + assert.strictEqual(code, 1); + done(); + }); + }); + it('should handle chdir() errors', (done) => { + const term = new UnixTerminal('/bin/echo', [], { cwd: '/nowhere' }); + term.on('exit', (code, signal) => { + assert.strictEqual(code, 1); + done(); + }); + }); it('should not leak child process', (done) => { const count = cp.execSync('ps -ax | grep node | wc -l'); const term = new UnixTerminal('node', [ '-e', ` diff --git a/src/unixTerminal.ts b/src/unixTerminal.ts index 985979a5c..d49080577 100644 --- a/src/unixTerminal.ts +++ b/src/unixTerminal.ts @@ -11,11 +11,14 @@ import { ArgvOrCommandLine } from './types'; import { assign } from './utils'; let pty: IUnixNative; +let helperPath: string; try { pty = require('../build/Release/pty.node'); + helperPath = '../build/Release/spawn-helper'; } catch (outerError) { try { pty = require('../build/Debug/pty.node'); + helperPath = '../build/Debug/spawn-helper'; } catch (innerError) { console.error('innerError', innerError); // Re-throw the exception from the Release require if the Debug require fails as well @@ -23,6 +26,10 @@ try { } } +helperPath = path.resolve(__dirname, helperPath); +helperPath = helperPath.replace('app.asar', 'app.asar.unpacked'); +helperPath = helperPath.replace('node_modules.asar', 'node_modules.asar.unpacked'); + const DEFAULT_FILE = 'sh'; const DEFAULT_NAME = 'xterm'; const DESTROY_SOCKET_TIMEOUT_MS = 200; @@ -104,7 +111,7 @@ export class UnixTerminal extends Terminal { }; // fork - const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), onexit); + const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit); this._socket = new PipeSocket(term.fd); if (encoding !== null) {