chore: 清理残余文件

This commit is contained in:
2026-03-21 20:26:20 +08:00
parent c265de6a84
commit fd70c1ad41
957 changed files with 117441 additions and 0 deletions

15
node_modules/node-pty/src/conpty_console_list_agent.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) 2019, Microsoft Corporation (MIT License).
*
* This module fetches the console process list for a particular PID. It must be
* called from a different process (child_process.fork) as there can only be a
* single console attached to a process.
*/
import { loadNativeModule } from './utils';
const getConsoleProcessList = loadNativeModule('conpty_console_list').module.getConsoleProcessList;
const shellPid = parseInt(process.argv[2], 10);
const consoleProcessList = getConsoleProcessList(shellPid);
process.send!({ consoleProcessList });
process.exit(0);

30
node_modules/node-pty/src/eventEmitter2.test.ts generated vendored Normal file
View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2019, Microsoft Corporation (MIT License).
*/
import * as assert from 'assert';
import { EventEmitter2 } from './eventEmitter2';
describe('EventEmitter2', () => {
it('should fire listeners multiple times', () => {
const order: string[] = [];
const emitter = new EventEmitter2<number>();
emitter.event(data => order.push(data + 'a'));
emitter.event(data => order.push(data + 'b'));
emitter.fire(1);
emitter.fire(2);
assert.deepEqual(order, [ '1a', '1b', '2a', '2b' ]);
});
it('should not fire listeners once disposed', () => {
const order: string[] = [];
const emitter = new EventEmitter2<number>();
emitter.event(data => order.push(data + 'a'));
const disposeB = emitter.event(data => order.push(data + 'b'));
emitter.event(data => order.push(data + 'c'));
emitter.fire(1);
disposeB.dispose();
emitter.fire(2);
assert.deepEqual(order, [ '1a', '1b', '1c', '2a', '2c' ]);
});
});

48
node_modules/node-pty/src/eventEmitter2.ts generated vendored Normal file
View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2019, Microsoft Corporation (MIT License).
*/
import { IDisposable } from './types';
interface IListener<T> {
(e: T): void;
}
export interface IEvent<T> {
(listener: (e: T) => any): IDisposable;
}
export class EventEmitter2<T> {
private _listeners: IListener<T>[] = [];
private _event?: IEvent<T>;
public get event(): IEvent<T> {
if (!this._event) {
this._event = (listener: (e: T) => any) => {
this._listeners.push(listener);
const disposable = {
dispose: () => {
for (let i = 0; i < this._listeners.length; i++) {
if (this._listeners[i] === listener) {
this._listeners.splice(i, 1);
return;
}
}
}
};
return disposable;
};
}
return this._event;
}
public fire(data: T): void {
const queue: IListener<T>[] = [];
for (let i = 0; i < this._listeners.length; i++) {
queue.push(this._listeners[i]);
}
for (let i = 0; i < queue.length; i++) {
queue[i].call(undefined, data);
}
}
}

52
node_modules/node-pty/src/index.ts generated vendored Normal file
View File

@@ -0,0 +1,52 @@
/**
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import { ITerminal, IPtyOpenOptions, IPtyForkOptions, IWindowsPtyForkOptions } from './interfaces';
import { ArgvOrCommandLine } from './types';
import { loadNativeModule } from './utils';
let terminalCtor: any;
if (process.platform === 'win32') {
terminalCtor = require('./windowsTerminal').WindowsTerminal;
} else {
terminalCtor = require('./unixTerminal').UnixTerminal;
}
/**
* Forks a process as a pseudoterminal.
* @param file The file to launch.
* @param args The file's arguments as argv (string[]) or in a pre-escaped
* CommandLine format (string). Note that the CommandLine option is only
* available on Windows and is expected to be escaped properly.
* @param options The options of the terminal.
* @throws When the file passed to spawn with does not exists.
* @see CommandLineToArgvW https://msdn.microsoft.com/en-us/library/windows/desktop/bb776391(v=vs.85).aspx
* @see Parsing C++ Comamnd-Line Arguments https://msdn.microsoft.com/en-us/library/17w5ykft.aspx
* @see GetCommandLine https://msdn.microsoft.com/en-us/library/windows/desktop/ms683156.aspx
*/
export function spawn(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {
return new terminalCtor(file, args, opt);
}
/** @deprecated */
export function fork(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {
return new terminalCtor(file, args, opt);
}
/** @deprecated */
export function createTerminal(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions | IWindowsPtyForkOptions): ITerminal {
return new terminalCtor(file, args, opt);
}
export function open(options: IPtyOpenOptions): ITerminal {
return terminalCtor.open(options);
}
/**
* Expose the native API when not Windows, note that this is not public API and
* could be removed at any time.
*/
export const native = (process.platform !== 'win32' ? loadNativeModule('pty').module : null);

130
node_modules/node-pty/src/interfaces.ts generated vendored Normal file
View File

@@ -0,0 +1,130 @@
/**
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
export interface IProcessEnv {
[key: string]: string | undefined;
}
export interface ITerminal {
/**
* Gets the name of the process.
*/
process: string;
/**
* Gets the process ID.
*/
pid: number;
/**
* Writes data to the socket.
* @param data The data to write.
*/
write(data: string | Buffer): void;
/**
* Resize the pty.
* @param cols The number of columns.
* @param rows The number of rows.
*/
resize(cols: number, rows: number): void;
/**
* Clears the pty's internal representation of its buffer. This is a no-op
* unless on Windows/ConPTY.
*/
clear(): void;
/**
* Close, kill and destroy the socket.
*/
destroy(): void;
/**
* Kill the pty.
* @param signal The signal to send, by default this is SIGHUP. This is not
* supported on Windows.
*/
kill(signal?: string): void;
/**
* Set the pty socket encoding.
*/
setEncoding(encoding: string | null): void;
/**
* Resume the pty socket.
*/
resume(): void;
/**
* Pause the pty socket.
*/
pause(): void;
/**
* Alias for ITerminal.on(eventName, listener).
*/
addListener(eventName: string, listener: (...args: any[]) => any): void;
/**
* Adds the listener function to the end of the listeners array for the event
* named eventName.
* @param eventName The event name.
* @param listener The callback function
*/
on(eventName: string, listener: (...args: any[]) => any): void;
/**
* Returns a copy of the array of listeners for the event named eventName.
*/
listeners(eventName: string): Function[];
/**
* Removes the specified listener from the listener array for the event named
* eventName.
*/
removeListener(eventName: string, listener: (...args: any[]) => any): void;
/**
* Removes all listeners, or those of the specified eventName.
*/
removeAllListeners(eventName: string): void;
/**
* Adds a one time listener function for the event named eventName. The next
* time eventName is triggered, this listener is removed and then invoked.
*/
once(eventName: string, listener: (...args: any[]) => any): void;
}
interface IBasePtyForkOptions {
name?: string;
cols?: number;
rows?: number;
cwd?: string;
env?: IProcessEnv;
encoding?: string | null;
handleFlowControl?: boolean;
flowControlPause?: string;
flowControlResume?: string;
}
export interface IPtyForkOptions extends IBasePtyForkOptions {
uid?: number;
gid?: number;
}
export interface IWindowsPtyForkOptions extends IBasePtyForkOptions {
useConpty?: boolean;
useConptyDll?: boolean;
conptyInheritCursor?: boolean;
}
export interface IPtyOpenOptions {
cols?: number;
rows?: number;
encoding?: string | null;
}

54
node_modules/node-pty/src/native.d.ts generated vendored Normal file
View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
interface IConptyNative {
startProcess(file: string, cols: number, rows: number, debug: boolean, pipeName: string, conptyInheritCursor: boolean, useConptyDll: boolean): IConptyProcess;
connect(ptyId: number, commandLine: string, cwd: string, env: string[], useConptyDll: boolean, onExitCallback: (exitCode: number) => void): { pid: number };
resize(ptyId: number, cols: number, rows: number, useConptyDll: boolean): void;
clear(ptyId: number, useConptyDll: boolean): void;
kill(ptyId: number, useConptyDll: boolean): void;
}
interface IWinptyNative {
startProcess(file: string, commandLine: string, env: string[], cwd: string, cols: number, rows: number, debug: boolean): IWinptyProcess;
resize(pid: number, cols: number, rows: number): void;
kill(pid: number, innerPid: number): void;
getProcessList(pid: number): number[];
getExitCode(innerPid: number): number;
}
interface IUnixNative {
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;
resize(fd: number, cols: number, rows: number): void;
}
interface IConptyProcess {
pty: number;
fd: number;
conin: string;
conout: string;
}
interface IWinptyProcess {
pty: number;
fd: number;
conin: string;
conout: string;
pid: number;
innerPid: number;
}
interface IUnixProcess {
fd: number;
pid: number;
pty: string;
}
interface IUnixOpenProcess {
master: number;
slave: number;
pty: string;
}

15
node_modules/node-pty/src/shared/conout.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) 2020, Microsoft Corporation (MIT License).
*/
export interface IWorkerData {
conoutPipeName: string;
}
export const enum ConoutWorkerMessage {
READY = 1
}
export function getWorkerPipeName(conoutPipeName: string): string {
return `${conoutPipeName}-worker`;
}

119
node_modules/node-pty/src/terminal.test.ts generated vendored Normal file
View File

@@ -0,0 +1,119 @@
/**
* Copyright (c) 2017, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import * as assert from 'assert';
import { WindowsTerminal } from './windowsTerminal';
import { UnixTerminal } from './unixTerminal';
import { Terminal } from './terminal';
import { Socket } from 'net';
const terminalConstructor = (process.platform === 'win32') ? WindowsTerminal : UnixTerminal;
const SHELL = (process.platform === 'win32') ? 'cmd.exe' : '/bin/bash';
let terminalCtor: WindowsTerminal | UnixTerminal;
if (process.platform === 'win32') {
terminalCtor = require('./windowsTerminal');
} else {
terminalCtor = require('./unixTerminal');
}
class TestTerminal extends Terminal {
public checkType<T>(name: string, value: T, type: string, allowArray: boolean = false): void {
this._checkType(name, value, type, allowArray);
}
protected _write(data: string | Buffer): void {
throw new Error('Method not implemented.');
}
public resize(cols: number, rows: number): void {
throw new Error('Method not implemented.');
}
public clear(): void {
throw new Error('Method not implemented.');
}
public destroy(): void {
throw new Error('Method not implemented.');
}
public kill(signal?: string): void {
throw new Error('Method not implemented.');
}
public get process(): string {
throw new Error('Method not implemented.');
}
public get master(): Socket {
throw new Error('Method not implemented.');
}
public get slave(): Socket {
throw new Error('Method not implemented.');
}
}
describe('Terminal', () => {
describe('constructor', () => {
it('should do basic type checks', () => {
assert.throws(
() => new (<any>terminalCtor)('a', 'b', { 'name': {} }),
'name must be a string (not a object)'
);
});
});
describe('checkType', () => {
it('should throw for the wrong type', () => {
const t = new TestTerminal();
assert.doesNotThrow(() => t.checkType('foo', 'test', 'string'));
assert.doesNotThrow(() => t.checkType('foo', 1, 'number'));
assert.doesNotThrow(() => t.checkType('foo', {}, 'object'));
assert.throws(() => t.checkType('foo', 'test', 'number'));
assert.throws(() => t.checkType('foo', 1, 'object'));
assert.throws(() => t.checkType('foo', {}, 'string'));
});
it('should throw for wrong types within arrays', () => {
const t = new TestTerminal();
assert.doesNotThrow(() => t.checkType('foo', ['test'], 'string', true));
assert.doesNotThrow(() => t.checkType('foo', [1], 'number', true));
assert.doesNotThrow(() => t.checkType('foo', [{}], 'object', true));
assert.throws(() => t.checkType('foo', ['test'], 'number', true));
assert.throws(() => t.checkType('foo', [1], 'object', true));
assert.throws(() => t.checkType('foo', [{}], 'string', true));
});
});
describe('automatic flow control', () => {
it('should respect ctor flow control options', () => {
const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'abc', flowControlResume: '123'});
assert.equal(pty.handleFlowControl, true);
assert.equal((pty as any)._flowControlPause, 'abc');
assert.equal((pty as any)._flowControlResume, '123');
});
// TODO: I don't think this test ever worked due to pollUntil being used incorrectly
// it('should do flow control automatically', async function(): Promise<void> {
// // Flow control doesn't work on Windows
// if (process.platform === 'win32') {
// return;
// }
// this.timeout(10000);
// const pty = new terminalConstructor(SHELL, [], {handleFlowControl: true, flowControlPause: 'PAUSE', flowControlResume: 'RESUME'});
// let read: string = '';
// pty.on('data', data => read += data);
// pty.on('pause', () => read += 'paused');
// pty.on('resume', () => read += 'resumed');
// pty.write('1');
// pty.write('PAUSE');
// pty.write('2');
// pty.write('RESUME');
// pty.write('3');
// await pollUntil(() => {
// return stripEscapeSequences(read).endsWith('1pausedresumed23');
// }, 100, 10);
// });
});
});
function stripEscapeSequences(data: string): string {
return data.replace(/\u001b\[0K/, '');
}

211
node_modules/node-pty/src/terminal.ts generated vendored Normal file
View File

@@ -0,0 +1,211 @@
/**
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import { Socket } from 'net';
import { EventEmitter } from 'events';
import { ITerminal, IPtyForkOptions, IProcessEnv } from './interfaces';
import { EventEmitter2, IEvent } from './eventEmitter2';
import { IExitEvent } from './types';
export const DEFAULT_COLS: number = 80;
export const DEFAULT_ROWS: number = 24;
/**
* Default messages to indicate PAUSE/RESUME for automatic flow control.
* To avoid conflicts with rebound XON/XOFF control codes (such as on-my-zsh),
* the sequences can be customized in `IPtyForkOptions`.
*/
const FLOW_CONTROL_PAUSE = '\x13'; // defaults to XOFF
const FLOW_CONTROL_RESUME = '\x11'; // defaults to XON
export abstract class Terminal implements ITerminal {
protected _socket!: Socket; // HACK: This is unsafe
protected _pid: number = 0;
protected _fd: number = 0;
protected _pty: any;
protected _file!: string; // HACK: This is unsafe
protected _name!: string; // HACK: This is unsafe
protected _cols: number = 0;
protected _rows: number = 0;
protected _readable: boolean = false;
protected _writable: boolean = false;
protected _internalee: EventEmitter;
private _flowControlPause: string;
private _flowControlResume: string;
public handleFlowControl: boolean;
private _onData = new EventEmitter2<string>();
public get onData(): IEvent<string> { return this._onData.event; }
private _onExit = new EventEmitter2<IExitEvent>();
public get onExit(): IEvent<IExitEvent> { return this._onExit.event; }
public get pid(): number { return this._pid; }
public get cols(): number { return this._cols; }
public get rows(): number { return this._rows; }
constructor(opt?: IPtyForkOptions) {
// for 'close'
this._internalee = new EventEmitter();
// setup flow control handling
this.handleFlowControl = !!(opt?.handleFlowControl);
this._flowControlPause = opt?.flowControlPause || FLOW_CONTROL_PAUSE;
this._flowControlResume = opt?.flowControlResume || FLOW_CONTROL_RESUME;
if (!opt) {
return;
}
// Do basic type checks here in case node-pty is being used within JavaScript. If the wrong
// types go through to the C++ side it can lead to hard to diagnose exceptions.
this._checkType('name', opt.name ? opt.name : undefined, 'string');
this._checkType('cols', opt.cols ? opt.cols : undefined, 'number');
this._checkType('rows', opt.rows ? opt.rows : undefined, 'number');
this._checkType('cwd', opt.cwd ? opt.cwd : undefined, 'string');
this._checkType('env', opt.env ? opt.env : undefined, 'object');
this._checkType('uid', opt.uid ? opt.uid : undefined, 'number');
this._checkType('gid', opt.gid ? opt.gid : undefined, 'number');
this._checkType('encoding', opt.encoding ? opt.encoding : undefined, 'string');
}
protected abstract _write(data: string | Buffer): void;
public write(data: string | Buffer): void {
if (this.handleFlowControl) {
// PAUSE/RESUME messages are not forwarded to the pty
if (data === this._flowControlPause) {
this.pause();
return;
}
if (data === this._flowControlResume) {
this.resume();
return;
}
}
// everything else goes to the real pty
this._write(data);
}
protected _forwardEvents(): void {
this.on('data', e => this._onData.fire(e));
this.on('exit', (exitCode, signal) => this._onExit.fire({ exitCode, signal }));
}
protected _checkType<T>(name: string, value: T | undefined, type: string, allowArray: boolean = false): void {
if (value === undefined) {
return;
}
if (allowArray) {
if (Array.isArray(value)) {
value.forEach((v, i) => {
if (typeof v !== type) {
throw new Error(`${name}[${i}] must be a ${type} (not a ${typeof v[i]})`);
}
});
return;
}
}
if (typeof value !== type) {
throw new Error(`${name} must be a ${type} (not a ${typeof value})`);
}
}
/** See net.Socket.end */
public end(data: string): void {
this._socket.end(data);
}
/** See stream.Readable.pipe */
public pipe(dest: any, options: any): any {
return this._socket.pipe(dest, options);
}
/** See net.Socket.pause */
public pause(): Socket {
return this._socket.pause();
}
/** See net.Socket.resume */
public resume(): Socket {
return this._socket.resume();
}
/** See net.Socket.setEncoding */
public setEncoding(encoding: string | null): void {
if ((this._socket as any)._decoder) {
delete (this._socket as any)._decoder;
}
if (encoding) {
this._socket.setEncoding(encoding);
}
}
public addListener(eventName: string, listener: (...args: any[]) => any): void { this.on(eventName, listener); }
public on(eventName: string, listener: (...args: any[]) => any): void {
if (eventName === 'close') {
this._internalee.on('close', listener);
return;
}
this._socket.on(eventName, listener);
}
public emit(eventName: string, ...args: any[]): any {
if (eventName === 'close') {
return this._internalee.emit.apply(this._internalee, arguments as any);
}
return this._socket.emit.apply(this._socket, arguments as any);
}
public listeners(eventName: string): Function[] {
return this._socket.listeners(eventName);
}
public removeListener(eventName: string, listener: (...args: any[]) => any): void {
this._socket.removeListener(eventName, listener);
}
public removeAllListeners(eventName: string): void {
this._socket.removeAllListeners(eventName);
}
public once(eventName: string, listener: (...args: any[]) => any): void {
this._socket.once(eventName, listener);
}
public abstract resize(cols: number, rows: number): void;
public abstract clear(): void;
public abstract destroy(): void;
public abstract kill(signal?: string): void;
public abstract get process(): string;
public abstract get master(): Socket| undefined;
public abstract get slave(): Socket | undefined;
protected _close(): void {
this._socket.readable = false;
this.write = () => {};
this.end = () => {};
this._writable = false;
this._readable = false;
}
protected _parseEnv(env: IProcessEnv): string[] {
const keys = Object.keys(env || {});
const pairs = [];
for (let i = 0; i < keys.length; i++) {
if (keys[i] === undefined) {
continue;
}
pairs.push(keys[i] + '=' + env[keys[i]]);
}
return pairs;
}
}

23
node_modules/node-pty/src/testUtils.test.ts generated vendored Normal file
View File

@@ -0,0 +1,23 @@
/**
* Copyright (c) 2019, Microsoft Corporation (MIT License).
*/
export function pollUntil(cb: () => boolean, timeout: number, interval: number): Promise<void> {
return new Promise<void>((resolve, reject) => {
const intervalId = setInterval(() => {
if (cb()) {
clearInterval(intervalId);
clearTimeout(timeoutId);
resolve();
}
}, interval);
const timeoutId = setTimeout(() => {
clearInterval(intervalId);
if (cb()) {
resolve();
} else {
reject();
}
}, timeout);
});
}

22
node_modules/node-pty/src/tsconfig.json generated vendored Normal file
View File

@@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es5",
"rootDir": ".",
"outDir": "../lib",
"sourceMap": true,
"lib": [
"es2015"
],
"strict": true
},
"exclude": [
"node_modules",
"scripts",
"index.js",
"demo.js",
"lib",
"test",
"examples"
]
}

15
node_modules/node-pty/src/types.ts generated vendored Normal file
View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) 2017, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
export type ArgvOrCommandLine = string[] | string;
export interface IExitEvent {
exitCode: number;
signal: number | undefined;
}
export interface IDisposable {
dispose(): void;
}

799
node_modules/node-pty/src/unix/pty.cc generated vendored Normal file
View File

@@ -0,0 +1,799 @@
/**
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
* Copyright (c) 2017, Daniel Imms (MIT License)
*
* pty.cc:
* This file is responsible for starting processes
* with pseudo-terminal file descriptors.
*
* See:
* man pty
* man tty_ioctl
* man termios
* man forkpty
*/
/**
* Includes
*/
#define NODE_ADDON_API_DISABLE_DEPRECATED
#include <napi.h>
#include <assert.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <thread>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <signal.h>
/* forkpty */
/* http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html */
#if defined(__linux__)
#include <pty.h>
#elif defined(__APPLE__)
#include <util.h>
#elif defined(__FreeBSD__)
#include <libutil.h>
#include <termios.h>
#elif defined(__OpenBSD__)
#include <util.h>
#include <termios.h>
#endif
/* Some platforms name VWERASE and VDISCARD differently */
#if !defined(VWERASE) && defined(VWERSE)
#define VWERASE VWERSE
#endif
#if !defined(VDISCARD) && defined(VDISCRD)
#define VDISCARD VDISCRD
#endif
/* for pty_getproc */
#if defined(__linux__)
#include <stdio.h>
#include <stdint.h>
#elif defined(__APPLE__)
#include <libproc.h>
#include <os/availability.h>
#include <paths.h>
#include <spawn.h>
#include <sys/event.h>
#include <sys/sysctl.h>
#include <termios.h>
#endif
/* NSIG - macro for highest signal + 1, should be defined */
#ifndef NSIG
#define NSIG 32
#endif
/* macOS 10.14 back does not define this constant */
#ifndef POSIX_SPAWN_SETSID
#define POSIX_SPAWN_SETSID 1024
#endif
/* environ for execvpe */
/* node/src/node_child_process.cc */
#if !defined(__APPLE__)
extern char **environ;
#endif
#if defined(__APPLE__)
extern "C" {
// Changes the current thread's directory to a path or directory file
// descriptor. libpthread only exposes a syscall wrapper starting in
// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
// the syscall is issued directly.
int pthread_chdir_np(const char* dir) API_AVAILABLE(macosx(10.12));
int pthread_fchdir_np(int fd) API_AVAILABLE(macosx(10.12));
}
#define HANDLE_EINTR(x) ({ \
int eintr_wrapper_counter = 0; \
decltype(x) eintr_wrapper_result; \
do { \
eintr_wrapper_result = (x); \
} while (eintr_wrapper_result == -1 && errno == EINTR && \
eintr_wrapper_counter++ < 100); \
eintr_wrapper_result; \
})
#endif
struct ExitEvent {
int exit_code = 0, signal_code = 0;
};
void SetupExitCallback(Napi::Env env, Napi::Function cb, pid_t pid) {
std::thread *th = new std::thread;
// Don't use Napi::AsyncWorker which is limited by UV_THREADPOOL_SIZE.
auto tsfn = Napi::ThreadSafeFunction::New(
env,
cb, // JavaScript function called asynchronously
"SetupExitCallback_resource", // Name
0, // Unlimited queue
1, // Only one thread will use this initially
[th](Napi::Env) { // Finalizer used to clean threads up
th->join();
delete th;
});
*th = std::thread([tsfn = std::move(tsfn), pid] {
auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {
cb.Call({Napi::Number::New(env, exit_event->exit_code),
Napi::Number::New(env, exit_event->signal_code)});
delete exit_event;
};
int ret;
int stat_loc;
#if defined(__APPLE__)
// Based on
// https://source.chromium.org/chromium/chromium/src/+/main:base/process/kill_mac.cc;l=35-69?
int kq = HANDLE_EINTR(kqueue());
struct kevent change = {0};
EV_SET(&change, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, NULL);
ret = HANDLE_EINTR(kevent(kq, &change, 1, NULL, 0, NULL));
if (ret == -1) {
if (errno == ESRCH) {
// At this point, one of the following has occurred:
// 1. The process has died but has not yet been reaped.
// 2. The process has died and has already been reaped.
// 3. The process is in the process of dying. It's no longer
// kqueueable, but it may not be waitable yet either. Mark calls
// this case the "zombie death race".
ret = HANDLE_EINTR(waitpid(pid, &stat_loc, WNOHANG));
if (ret == 0) {
ret = kill(pid, SIGKILL);
if (ret != -1) {
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
}
}
}
} else {
struct kevent event = {0};
ret = HANDLE_EINTR(kevent(kq, NULL, 0, &event, 1, NULL));
if (ret == 1) {
if ((event.fflags & NOTE_EXIT) &&
(event.ident == static_cast<uintptr_t>(pid))) {
// The process is dead or dying. This won't block for long, if at
// all.
HANDLE_EINTR(waitpid(pid, &stat_loc, 0));
}
}
}
#else
while (true) {
errno = 0;
if ((ret = waitpid(pid, &stat_loc, 0)) != pid) {
if (ret == -1 && errno == EINTR) {
continue;
}
if (ret == -1 && errno == ECHILD) {
// XXX node v0.8.x seems to have this problem.
// waitpid is already handled elsewhere.
;
} else {
assert(false);
}
}
break;
}
#endif
ExitEvent *exit_event = new ExitEvent;
if (WIFEXITED(stat_loc)) {
exit_event->exit_code = WEXITSTATUS(stat_loc); // errno?
}
if (WIFSIGNALED(stat_loc)) {
exit_event->signal_code = WTERMSIG(stat_loc);
}
auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
switch (status) {
case napi_closing:
break;
case napi_queue_full:
Napi::Error::Fatal("SetupExitCallback", "Queue was full");
case napi_ok:
if (tsfn.Release() != napi_ok) {
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.Release() failed");
}
break;
default:
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.BlockingCall() failed");
}
});
}
/**
* Methods
*/
Napi::Value PtyFork(const Napi::CallbackInfo& info);
Napi::Value PtyOpen(const Napi::CallbackInfo& info);
Napi::Value PtyResize(const Napi::CallbackInfo& info);
Napi::Value PtyGetProc(const Napi::CallbackInfo& info);
/**
* Functions
*/
static int
pty_nonblock(int);
#if defined(__APPLE__)
static char *
pty_getproc(int);
#else
static char *
pty_getproc(int, char *);
#endif
#if defined(__APPLE__) || defined(__OpenBSD__)
static void
pty_posix_spawn(char** argv, char** env,
const struct termios *termp,
const struct winsize *winp,
int* master,
pid_t* pid,
int* err);
#endif
struct DelBuf {
int len;
DelBuf(int len) : len(len) {}
void operator()(char **p) {
if (p == nullptr)
return;
for (int i = 0; i < len; i++)
free(p[i]);
delete[] p;
}
};
Napi::Value PtyFork(const Napi::CallbackInfo& info) {
Napi::Env napiEnv(info.Env());
Napi::HandleScope scope(napiEnv);
if (info.Length() != 11 ||
!info[0].IsString() ||
!info[1].IsArray() ||
!info[2].IsArray() ||
!info[3].IsString() ||
!info[4].IsNumber() ||
!info[5].IsNumber() ||
!info[6].IsNumber() ||
!info[7].IsNumber() ||
!info[8].IsBoolean() ||
!info[9].IsString() ||
!info[10].IsFunction()) {
throw Napi::Error::New(napiEnv, "Usage: pty.fork(file, args, env, cwd, cols, rows, uid, gid, utf8, helperPath, onexit)");
}
// file
std::string file = info[0].As<Napi::String>();
// args
Napi::Array argv_ = info[1].As<Napi::Array>();
// env
Napi::Array env_ = info[2].As<Napi::Array>();
int envc = env_.Length();
std::unique_ptr<char *, DelBuf> env_unique_ptr(new char *[envc + 1], DelBuf(envc + 1));
char **env = env_unique_ptr.get();
env[envc] = NULL;
for (int i = 0; i < envc; i++) {
std::string pair = env_.Get(i).As<Napi::String>();
env[i] = strdup(pair.c_str());
}
// cwd
std::string cwd_ = info[3].As<Napi::String>();
// size
struct winsize winp;
winp.ws_col = info[4].As<Napi::Number>().Int32Value();
winp.ws_row = info[5].As<Napi::Number>().Int32Value();
winp.ws_xpixel = 0;
winp.ws_ypixel = 0;
#if !defined(__APPLE__)
// uid / gid
int uid = info[6].As<Napi::Number>().Int32Value();
int gid = info[7].As<Napi::Number>().Int32Value();
#endif
// termios
struct termios t = termios();
struct termios *term = &t;
term->c_iflag = ICRNL | IXON | IXANY | IMAXBEL | BRKINT;
if (info[8].As<Napi::Boolean>().Value()) {
#if defined(IUTF8)
term->c_iflag |= IUTF8;
#endif
}
term->c_oflag = OPOST | ONLCR;
term->c_cflag = CREAD | CS8 | HUPCL;
term->c_lflag = ICANON | ISIG | IEXTEN | ECHO | ECHOE | ECHOK | ECHOKE | ECHOCTL;
term->c_cc[VEOF] = 4;
term->c_cc[VEOL] = -1;
term->c_cc[VEOL2] = -1;
term->c_cc[VERASE] = 0x7f;
term->c_cc[VWERASE] = 23;
term->c_cc[VKILL] = 21;
term->c_cc[VREPRINT] = 18;
term->c_cc[VINTR] = 3;
term->c_cc[VQUIT] = 0x1c;
term->c_cc[VSUSP] = 26;
term->c_cc[VSTART] = 17;
term->c_cc[VSTOP] = 19;
term->c_cc[VLNEXT] = 22;
term->c_cc[VDISCARD] = 15;
term->c_cc[VMIN] = 1;
term->c_cc[VTIME] = 0;
#if (__APPLE__)
term->c_cc[VDSUSP] = 25;
term->c_cc[VSTATUS] = 20;
#endif
cfsetispeed(term, B38400);
cfsetospeed(term, B38400);
// helperPath
std::string helper_path = info[9].As<Napi::String>();
pid_t pid;
int master;
#if defined(__APPLE__)
int argc = argv_.Length();
int argl = argc + 4;
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
char **argv = argv_unique_ptr.get();
argv[0] = strdup(helper_path.c_str());
argv[1] = strdup(cwd_.c_str());
argv[2] = strdup(file.c_str());
argv[argl - 1] = NULL;
for (int i = 0; i < argc; i++) {
std::string arg = argv_.Get(i).As<Napi::String>();
argv[i + 3] = strdup(arg.c_str());
}
int err = -1;
pty_posix_spawn(argv, env, term, &winp, &master, &pid, &err);
if (err != 0) {
throw Napi::Error::New(napiEnv, "posix_spawnp failed.");
}
if (pty_nonblock(master) == -1) {
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
}
#else
int argc = argv_.Length();
int argl = argc + 2;
std::unique_ptr<char *, DelBuf> argv_unique_ptr(new char *[argl], DelBuf(argl));
char** argv = argv_unique_ptr.get();
argv[0] = strdup(file.c_str());
argv[argl - 1] = NULL;
for (int i = 0; i < argc; i++) {
std::string arg = argv_.Get(i).As<Napi::String>();
argv[i + 1] = strdup(arg.c_str());
}
sigset_t newmask, oldmask;
struct sigaction sig_action;
// temporarily block all signals
// this is needed due to a race condition in openpty
// and to avoid running signal handlers in the child
// before exec* happened
sigfillset(&newmask);
pthread_sigmask(SIG_SETMASK, &newmask, &oldmask);
pid = forkpty(&master, nullptr, static_cast<termios*>(term), static_cast<winsize*>(&winp));
if (!pid) {
// remove all signal handler from child
sig_action.sa_handler = SIG_DFL;
sig_action.sa_flags = 0;
sigemptyset(&sig_action.sa_mask);
for (int i = 0 ; i < NSIG ; i++) { // NSIG is a macro for all signals + 1
sigaction(i, &sig_action, NULL);
}
}
// reenable signals
pthread_sigmask(SIG_SETMASK, &oldmask, NULL);
switch (pid) {
case -1:
throw Napi::Error::New(napiEnv, "forkpty(3) failed.");
case 0:
if (strlen(cwd_.c_str())) {
if (chdir(cwd_.c_str()) == -1) {
perror("chdir(2) failed.");
_exit(1);
}
}
if (uid != -1 && gid != -1) {
if (setgid(gid) == -1) {
perror("setgid(2) failed.");
_exit(1);
}
if (setuid(uid) == -1) {
perror("setuid(2) failed.");
_exit(1);
}
}
{
char **old = environ;
environ = env;
execvp(argv[0], argv);
environ = old;
perror("execvp(3) failed.");
_exit(1);
}
default:
if (pty_nonblock(master) == -1) {
throw Napi::Error::New(napiEnv, "Could not set master fd to nonblocking.");
}
}
#endif
Napi::Object obj = Napi::Object::New(napiEnv);
obj.Set("fd", Napi::Number::New(napiEnv, master));
obj.Set("pid", Napi::Number::New(napiEnv, pid));
obj.Set("pty", Napi::String::New(napiEnv, ptsname(master)));
// Set up process exit callback.
Napi::Function cb = info[10].As<Napi::Function>();
SetupExitCallback(napiEnv, cb, pid);
return obj;
}
Napi::Value PtyOpen(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 2 ||
!info[0].IsNumber() ||
!info[1].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.open(cols, rows)");
}
// size
struct winsize winp;
winp.ws_col = info[0].As<Napi::Number>().Int32Value();
winp.ws_row = info[1].As<Napi::Number>().Int32Value();
winp.ws_xpixel = 0;
winp.ws_ypixel = 0;
// pty
int master, slave;
int ret = openpty(&master, &slave, nullptr, NULL, static_cast<winsize*>(&winp));
if (ret == -1) {
throw Napi::Error::New(env, "openpty(3) failed.");
}
if (pty_nonblock(master) == -1) {
throw Napi::Error::New(env, "Could not set master fd to nonblocking.");
}
if (pty_nonblock(slave) == -1) {
throw Napi::Error::New(env, "Could not set slave fd to nonblocking.");
}
Napi::Object obj = Napi::Object::New(env);
obj.Set("master", Napi::Number::New(env, master));
obj.Set("slave", Napi::Number::New(env, slave));
obj.Set("pty", Napi::String::New(env, ptsname(master)));
return obj;
}
Napi::Value PtyResize(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 3 ||
!info[0].IsNumber() ||
!info[1].IsNumber() ||
!info[2].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.resize(fd, cols, rows)");
}
int fd = info[0].As<Napi::Number>().Int32Value();
struct winsize winp;
winp.ws_col = info[1].As<Napi::Number>().Int32Value();
winp.ws_row = info[2].As<Napi::Number>().Int32Value();
winp.ws_xpixel = 0;
winp.ws_ypixel = 0;
if (ioctl(fd, TIOCSWINSZ, &winp) == -1) {
switch (errno) {
case EBADF:
throw Napi::Error::New(env, "ioctl(2) failed, EBADF");
case EFAULT:
throw Napi::Error::New(env, "ioctl(2) failed, EFAULT");
case EINVAL:
throw Napi::Error::New(env, "ioctl(2) failed, EINVAL");
case ENOTTY:
throw Napi::Error::New(env, "ioctl(2) failed, ENOTTY");
}
throw Napi::Error::New(env, "ioctl(2) failed");
}
return env.Undefined();
}
/**
* Foreground Process Name
*/
Napi::Value PtyGetProc(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
#if defined(__APPLE__)
if (info.Length() != 1 ||
!info[0].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.process(pid)");
}
int fd = info[0].As<Napi::Number>().Int32Value();
char *name = pty_getproc(fd);
#else
if (info.Length() != 2 ||
!info[0].IsNumber() ||
!info[1].IsString()) {
throw Napi::Error::New(env, "Usage: pty.process(fd, tty)");
}
int fd = info[0].As<Napi::Number>().Int32Value();
std::string tty_ = info[1].As<Napi::String>();
char *tty = strdup(tty_.c_str());
char *name = pty_getproc(fd, tty);
free(tty);
#endif
if (name == NULL) {
return env.Undefined();
}
Napi::String name_ = Napi::String::New(env, name);
free(name);
return name_;
}
/**
* Nonblocking FD
*/
static int
pty_nonblock(int fd) {
int flags = fcntl(fd, F_GETFL, 0);
if (flags == -1) return -1;
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
/**
* pty_getproc
* Taken from tmux.
*/
// Taken from: tmux (http://tmux.sourceforge.net/)
// Copyright (c) 2009 Nicholas Marriott <nicm@users.sourceforge.net>
// Copyright (c) 2009 Joshua Elsasser <josh@elsasser.org>
// Copyright (c) 2009 Todd Carson <toc@daybefore.net>
//
// Permission to use, copy, modify, and distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
// ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
// WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
// IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
// OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
#if defined(__linux__)
static char *
pty_getproc(int fd, char *tty) {
FILE *f;
char *path, *buf;
size_t len;
int ch;
pid_t pgrp;
int r;
if ((pgrp = tcgetpgrp(fd)) == -1) {
return NULL;
}
r = asprintf(&path, "/proc/%lld/cmdline", (long long)pgrp);
if (r == -1 || path == NULL) return NULL;
if ((f = fopen(path, "r")) == NULL) {
free(path);
return NULL;
}
free(path);
len = 0;
buf = NULL;
while ((ch = fgetc(f)) != EOF) {
if (ch == '\0') break;
buf = (char *)realloc(buf, len + 2);
if (buf == NULL) return NULL;
buf[len++] = ch;
}
if (buf != NULL) {
buf[len] = '\0';
}
fclose(f);
return buf;
}
#elif defined(__APPLE__)
static char *
pty_getproc(int fd) {
int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_PID, 0 };
size_t size;
struct kinfo_proc kp;
if ((mib[3] = tcgetpgrp(fd)) == -1) {
return NULL;
}
size = sizeof kp;
if (sysctl(mib, 4, &kp, &size, NULL, 0) == -1) {
return NULL;
}
if (size != (sizeof kp) || *kp.kp_proc.p_comm == '\0') {
return NULL;
}
return strdup(kp.kp_proc.p_comm);
}
#else
static char *
pty_getproc(int fd, char *tty) {
return NULL;
}
#endif
#if defined(__APPLE__)
static void
pty_posix_spawn(char** argv, char** env,
const struct termios *termp,
const struct winsize *winp,
int* master,
pid_t* pid,
int* err) {
int low_fds[3];
size_t count = 0;
for (; count < 3; count++) {
low_fds[count] = posix_openpt(O_RDWR);
if (low_fds[count] >= STDERR_FILENO)
break;
}
int flags = POSIX_SPAWN_CLOEXEC_DEFAULT |
POSIX_SPAWN_SETSIGDEF |
POSIX_SPAWN_SETSIGMASK |
POSIX_SPAWN_SETSID;
*master = posix_openpt(O_RDWR);
if (*master == -1) {
return;
}
int res = grantpt(*master) || unlockpt(*master);
if (res == -1) {
return;
}
// Use TIOCPTYGNAME instead of ptsname() to avoid threading problems.
int slave;
char slave_pty_name[128];
res = ioctl(*master, TIOCPTYGNAME, slave_pty_name);
if (res == -1) {
return;
}
slave = open(slave_pty_name, O_RDWR | O_NOCTTY);
if (slave == -1) {
return;
}
if (termp) {
res = tcsetattr(slave, TCSANOW, termp);
if (res == -1) {
return;
};
}
if (winp) {
res = ioctl(slave, TIOCSWINSZ, winp);
if (res == -1) {
return;
}
}
posix_spawn_file_actions_t acts;
posix_spawn_file_actions_init(&acts);
posix_spawn_file_actions_adddup2(&acts, slave, STDIN_FILENO);
posix_spawn_file_actions_adddup2(&acts, slave, STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&acts, slave, STDERR_FILENO);
posix_spawn_file_actions_addclose(&acts, slave);
posix_spawn_file_actions_addclose(&acts, *master);
posix_spawnattr_t attrs;
posix_spawnattr_init(&attrs);
*err = posix_spawnattr_setflags(&attrs, flags);
if (*err != 0) {
goto done;
}
sigset_t signal_set;
/* Reset all signal the child to their default behavior */
sigfillset(&signal_set);
*err = posix_spawnattr_setsigdefault(&attrs, &signal_set);
if (*err != 0) {
goto done;
}
/* Reset the signal mask for all signals */
sigemptyset(&signal_set);
*err = posix_spawnattr_setsigmask(&attrs, &signal_set);
if (*err != 0) {
goto done;
}
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);
for (; count > 0; count--) {
close(low_fds[count]);
}
}
#endif
/**
* Init
*/
Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("fork", Napi::Function::New(env, PtyFork));
exports.Set("open", Napi::Function::New(env, PtyOpen));
exports.Set("resize", Napi::Function::New(env, PtyResize));
exports.Set("process", Napi::Function::New(env, PtyGetProc));
return exports;
}
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init)

23
node_modules/node-pty/src/unix/spawn-helper.cc generated vendored Normal file
View File

@@ -0,0 +1,23 @@
#include <errno.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
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;
}

367
node_modules/node-pty/src/unixTerminal.test.ts generated vendored Normal file
View File

@@ -0,0 +1,367 @@
/**
* Copyright (c) 2017, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import { UnixTerminal } from './unixTerminal';
import * as assert from 'assert';
import * as cp from 'child_process';
import * as path from 'path';
import * as tty from 'tty';
import * as fs from 'fs';
import { constants } from 'os';
import { pollUntil } from './testUtils.test';
import { pid } from 'process';
const FIXTURES_PATH = path.normalize(path.join(__dirname, '..', 'fixtures', 'utf8-character.txt'));
if (process.platform !== 'win32') {
describe('UnixTerminal', () => {
describe('Constructor', () => {
it('should set a valid pts name', () => {
const term = new UnixTerminal('/bin/bash', [], {});
let regExp: RegExp | undefined;
if (process.platform === 'linux') {
// https://linux.die.net/man/4/pts
regExp = /^\/dev\/pts\/\d+$/;
}
if (process.platform === 'darwin') {
// https://developer.apple.com/legacy/library/documentation/Darwin/Reference/ManPages/man4/pty.4.html
regExp = /^\/dev\/tty[p-sP-S][a-z0-9]+$/;
}
if (regExp) {
assert.ok(regExp.test(term.ptsName), '"' + term.ptsName + '" should match ' + regExp.toString());
}
assert.ok(tty.isatty(term.fd));
});
});
describe('PtyForkEncodingOption', () => {
it('should default to utf8', (done) => {
const term = new UnixTerminal('/bin/bash', [ '-c', `cat "${FIXTURES_PATH}"` ]);
term.on('data', (data) => {
assert.strictEqual(typeof data, 'string');
assert.strictEqual(data, '\u00E6');
done();
});
});
it('should return a Buffer when encoding is null', (done) => {
const term = new UnixTerminal('/bin/bash', [ '-c', `cat "${FIXTURES_PATH}"` ], {
encoding: null
});
term.on('data', (data) => {
assert.strictEqual(typeof data, 'object');
assert.ok(data instanceof Buffer);
assert.strictEqual(0xC3, data[0]);
assert.strictEqual(0xA6, data[1]);
done();
});
});
it('should support other encodings', (done) => {
const text = 'test æ!';
const term = new UnixTerminal(undefined, ['-c', 'echo "' + text + '"'], {
encoding: 'base64'
});
let buffer = '';
term.onData((data) => {
assert.strictEqual(typeof data, 'string');
buffer += data;
});
term.onExit(() => {
assert.strictEqual(Buffer.alloc(8, buffer, 'base64').toString().replace('\r', '').replace('\n', ''), text);
done();
});
});
});
describe('open', () => {
let term: UnixTerminal;
afterEach(() => {
if (term) {
term.slave!.destroy();
term.master!.destroy();
}
});
it('should open a pty with access to a master and slave socket', (done) => {
term = UnixTerminal.open({});
let slavebuf = '';
term.slave!.on('data', (data) => {
slavebuf += data;
});
let masterbuf = '';
term.master!.on('data', (data) => {
masterbuf += data;
});
pollUntil(() => {
if (masterbuf === 'slave\r\nmaster\r\n' && slavebuf === 'master\n') {
done();
return true;
}
return false;
}, 200, 10);
term.slave!.write('slave\n');
term.master!.write('master\n');
});
});
describe('close', () => {
const term = new UnixTerminal('node');
it('should exit when terminal is destroyed programmatically', (done) => {
term.on('exit', (code, signal) => {
assert.strictEqual(code, 0);
assert.strictEqual(signal, constants.signals.SIGHUP);
done();
});
term.destroy();
});
});
describe('signals in parent and child', () => {
it('SIGINT - custom in parent and child', done => {
// this test is cumbersome - we have to run it in a sub process to
// see behavior of SIGINT handlers
const data = `
var pty = require('./lib/index');
process.on('SIGINT', () => console.log('SIGINT in parent'));
var ptyProcess = pty.spawn('node', ['-e', 'process.on("SIGINT", ()=>console.log("SIGINT in child"));setTimeout(() => null, 300);'], {
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: process.env
});
ptyProcess.on('data', function (data) {
console.log(data);
});
setTimeout(() => null, 500);
console.log('ready', ptyProcess.pid);
`;
const buffer: string[] = [];
const p = cp.spawn('node', ['-e', data]);
let sub = '';
p.stdout.on('data', (data) => {
if (!data.toString().indexOf('ready')) {
sub = data.toString().split(' ')[1].slice(0, -1);
setTimeout(() => {
process.kill(parseInt(sub), 'SIGINT'); // SIGINT to child
p.kill('SIGINT'); // SIGINT to parent
}, 200);
} else {
buffer.push(data.toString().replace(/^\s+|\s+$/g, ''));
}
});
p.on('close', () => {
// handlers in parent and child should have been triggered
assert.strictEqual(buffer.indexOf('SIGINT in child') !== -1, true);
assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);
done();
});
});
it('SIGINT - custom in parent, default in child', done => {
// this tests the original idea of the signal(...) change in pty.cc:
// to make sure the SIGINT handler of a pty child is reset to default
// and does not interfere with the handler in the parent
const data = `
var pty = require('./lib/index');
process.on('SIGINT', () => console.log('SIGINT in parent'));
var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log("should not be printed"), 300);'], {
name: 'xterm-color',
cols: 80,
rows: 30,
cwd: process.env.HOME,
env: process.env
});
ptyProcess.on('data', function (data) {
console.log(data);
});
setTimeout(() => null, 500);
console.log('ready', ptyProcess.pid);
`;
const buffer: string[] = [];
const p = cp.spawn('node', ['-e', data]);
let sub = '';
p.stdout.on('data', (data) => {
if (!data.toString().indexOf('ready')) {
sub = data.toString().split(' ')[1].slice(0, -1);
setTimeout(() => {
process.kill(parseInt(sub), 'SIGINT'); // SIGINT to child
p.kill('SIGINT'); // SIGINT to parent
}, 200);
} else {
buffer.push(data.toString().replace(/^\s+|\s+$/g, ''));
}
});
p.on('close', () => {
// handlers in parent and child should have been triggered
assert.strictEqual(buffer.indexOf('should not be printed') !== -1, false);
assert.strictEqual(buffer.indexOf('SIGINT in parent') !== -1, true);
done();
});
});
it('SIGHUP default (child only)', done => {
const term = new UnixTerminal('node', [ '-e', `
console.log('ready');
setTimeout(()=>console.log('timeout'), 200);`
]);
let buffer = '';
term.on('data', (data) => {
if (data === 'ready\r\n') {
term.kill();
} else {
buffer += data;
}
});
term.on('exit', () => {
// no timeout in buffer
assert.strictEqual(buffer, '');
done();
});
});
it('SIGUSR1 - custom in parent and child', done => {
let pHandlerCalled = 0;
const handleSigUsr = function(h: any): any {
return function(): void {
pHandlerCalled += 1;
process.removeListener('SIGUSR1', h);
};
};
process.on('SIGUSR1', handleSigUsr(handleSigUsr));
const term = new UnixTerminal('node', [ '-e', `
process.on('SIGUSR1', () => {
console.log('SIGUSR1 in child');
});
console.log('ready');
setTimeout(()=>null, 200);`
]);
let buffer = '';
term.on('data', (data) => {
if (data === 'ready\r\n') {
process.kill(process.pid, 'SIGUSR1');
term.kill('SIGUSR1');
} else {
buffer += data;
}
});
term.on('exit', () => {
// should have called both handlers and only once
assert.strictEqual(pHandlerCalled, 1);
assert.strictEqual(buffer, 'SIGUSR1 in child\r\n');
done();
});
});
});
describe('spawn', () => {
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');
term.on('exit', () => done());
term.destroy();
});
it('should return the name of the sub process', (done) => {
const data = `
var pty = require('./lib/index');
var ptyProcess = pty.spawn('zsh', ['-c', 'python3'], {
env: process.env
});
ptyProcess.on('data', function (data) {
if (ptyProcess.process === 'Python') {
console.log('title', ptyProcess.process);
console.log('ready', ptyProcess.pid);
}
});
`;
const p = cp.spawn('node', ['-e', data]);
let sub = '';
let pid = '';
p.stdout.on('data', (data) => {
if (!data.toString().indexOf('title')) {
sub = data.toString().split(' ')[1].slice(0, -1);
} else if (!data.toString().indexOf('ready')) {
pid = data.toString().split(' ')[1].slice(0, -1);
process.kill(parseInt(pid), 'SIGINT');
p.kill('SIGINT');
}
});
p.on('exit', () => {
assert.notStrictEqual(pid, '');
assert.strictEqual(sub, 'Python');
done();
});
});
it('should close on exec', (done) => {
const data = `
var pty = require('./lib/index');
var ptyProcess = pty.spawn('node', ['-e', 'setTimeout(() => console.log("hello from terminal"), 300);']);
ptyProcess.on('data', function (data) {
console.log(data);
});
setTimeout(() => null, 500);
console.log('ready', ptyProcess.pid);
`;
const buffer: string[] = [];
const readFd = fs.openSync(FIXTURES_PATH, 'r');
const p = cp.spawn('node', ['-e', data], {
stdio: ['ignore', 'pipe', 'pipe', readFd]
});
let sub = '';
p.stdout!.on('data', (data) => {
if (!data.toString().indexOf('ready')) {
sub = data.toString().split(' ')[1].slice(0, -1);
try {
fs.statSync(`/proc/${sub}/fd/${readFd}`);
done('not reachable');
} catch (error) {
assert.notStrictEqual(error.message.indexOf('ENOENT'), -1);
}
setTimeout(() => {
process.kill(parseInt(sub), 'SIGINT'); // SIGINT to child
p.kill('SIGINT'); // SIGINT to parent
}, 200);
} else {
buffer.push(data.toString().replace(/^\s+|\s+$/g, ''));
}
});
p.on('close', () => {
done();
});
});
}
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', `
console.log('ready');
setTimeout(()=>console.log('timeout'), 200);`
]);
term.on('data', async (data) => {
if (data === 'ready\r\n') {
process.kill(term.pid, 'SIGINT');
await setTimeout(() => null, 1000);
const newCount = cp.execSync('ps -ax | grep node | wc -l');
assert.strictEqual(count.toString(), newCount.toString());
done();
}
});
});
});
});
}

388
node_modules/node-pty/src/unixTerminal.ts generated vendored Normal file
View File

@@ -0,0 +1,388 @@
/**
* Copyright (c) 2012-2015, Christopher Jeffrey (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import * as fs from 'fs';
import * as net from 'net';
import * as path from 'path';
import * as tty from 'tty';
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
import { IProcessEnv, IPtyForkOptions, IPtyOpenOptions } from './interfaces';
import { ArgvOrCommandLine, IDisposable } from './types';
import { assign, loadNativeModule } from './utils';
const native = loadNativeModule('pty');
const pty: IUnixNative = native.module;
let helperPath = native.dir + '/spawn-helper';
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;
export class UnixTerminal extends Terminal {
protected _fd: number;
protected _pty: string;
protected _file: string;
protected _name: string;
protected _readable: boolean;
protected _writable: boolean;
private _boundClose: boolean = false;
private _emittedClose: boolean = false;
private _writeStream: CustomWriteStream;
private _master: net.Socket | undefined;
private _slave: net.Socket | undefined;
public get master(): net.Socket | undefined { return this._master; }
public get slave(): net.Socket | undefined { return this._slave; }
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IPtyForkOptions) {
super(opt);
if (typeof args === 'string') {
throw new Error('args as a string is not supported on unix.');
}
// Initialize arguments
args = args || [];
file = file || DEFAULT_FILE;
opt = opt || {};
opt.env = opt.env || process.env;
this._cols = opt.cols || DEFAULT_COLS;
this._rows = opt.rows || DEFAULT_ROWS;
const uid = opt.uid ?? -1;
const gid = opt.gid ?? -1;
const env: IProcessEnv = assign({}, opt.env);
if (opt.env === process.env) {
this._sanitizeEnv(env);
}
const cwd = opt.cwd || process.cwd();
env.PWD = cwd;
const name = opt.name || env.TERM || DEFAULT_NAME;
env.TERM = name;
const parsedEnv = this._parseEnv(env);
const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
const onexit = (code: number, signal: number): void => {
// XXX Sometimes a data event is emitted after exit. Wait til socket is
// destroyed.
if (!this._emittedClose) {
if (this._boundClose) {
return;
}
this._boundClose = true;
// From macOS High Sierra 10.13.2 sometimes the socket never gets
// closed. A timeout is applied here to avoid the terminal never being
// destroyed when this occurs.
let timeout: NodeJS.Timeout | null = setTimeout(() => {
timeout = null;
// Destroying the socket now will cause the close event to fire
this._socket.destroy();
}, DESTROY_SOCKET_TIMEOUT_MS);
this.once('close', () => {
if (timeout !== null) {
clearTimeout(timeout);
}
this.emit('exit', code, signal);
});
return;
}
this.emit('exit', code, signal);
};
// fork
const term = pty.fork(file, args, parsedEnv, cwd, this._cols, this._rows, uid, gid, (encoding === 'utf8'), helperPath, onexit);
this._socket = new tty.ReadStream(term.fd);
if (encoding !== null) {
this._socket.setEncoding(encoding);
}
this._writeStream = new CustomWriteStream(term.fd, (encoding || undefined) as BufferEncoding);
// setup
this._socket.on('error', (err: any) => {
// NOTE: fs.ReadStream gets EAGAIN twice at first:
if (err.code) {
if (~err.code.indexOf('EAGAIN')) {
return;
}
}
// close
this._close();
// EIO on exit from fs.ReadStream:
if (!this._emittedClose) {
this._emittedClose = true;
this.emit('close');
}
// EIO, happens when someone closes our child process: the only process in
// the terminal.
// node < 0.6.14: errno 5
// node >= 0.6.14: read EIO
if (err.code) {
if (~err.code.indexOf('errno 5') || ~err.code.indexOf('EIO')) {
return;
}
}
// throw anything else
if (this.listeners('error').length < 2) {
throw err;
}
});
this._pid = term.pid;
this._fd = term.fd;
this._pty = term.pty;
this._file = file;
this._name = name;
this._readable = true;
this._writable = true;
this._socket.on('close', () => {
if (this._emittedClose) {
return;
}
this._emittedClose = true;
this._close();
this.emit('close');
});
this._forwardEvents();
}
protected _write(data: string | Buffer): void {
this._writeStream.write(data);
}
/* Accessors */
get fd(): number { return this._fd; }
get ptsName(): string { return this._pty; }
/**
* openpty
*/
public static open(opt: IPtyOpenOptions): UnixTerminal {
const self: UnixTerminal = Object.create(UnixTerminal.prototype);
opt = opt || {};
if (arguments.length > 1) {
opt = {
cols: arguments[1],
rows: arguments[2]
};
}
const cols = opt.cols || DEFAULT_COLS;
const rows = opt.rows || DEFAULT_ROWS;
const encoding = (opt.encoding === undefined ? 'utf8' : opt.encoding);
// open
const term: IUnixOpenProcess = pty.open(cols, rows);
self._master = new tty.ReadStream(term.master);
if (encoding !== null) {
self._master.setEncoding(encoding);
}
self._master.resume();
self._slave = new tty.ReadStream(term.slave);
if (encoding !== null) {
self._slave.setEncoding(encoding);
}
self._slave.resume();
self._socket = self._master;
self._pid = -1;
self._fd = term.master;
self._pty = term.pty;
self._file = process.argv[0] || 'node';
self._name = process.env.TERM || '';
self._readable = true;
self._writable = true;
self._socket.on('error', err => {
self._close();
if (self.listeners('error').length < 2) {
throw err;
}
});
self._socket.on('close', () => {
self._close();
});
return self;
}
public destroy(): void {
this._close();
// Need to close the read stream so node stops reading a dead file
// descriptor. Then we can safely SIGHUP the shell.
this._socket.once('close', () => {
this.kill('SIGHUP');
});
this._socket.destroy();
this._writeStream.dispose();
}
public kill(signal?: string): void {
try {
process.kill(this.pid, signal || 'SIGHUP');
} catch (e) { /* swallow */ }
}
/**
* Gets the name of the process.
*/
public get process(): string {
if (process.platform === 'darwin') {
const title = pty.process(this._fd);
return (title !== 'kernel_task') ? title : this._file;
}
return pty.process(this._fd, this._pty) || this._file;
}
/**
* TTY
*/
public resize(cols: number, rows: number): void {
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
throw new Error('resizing must be done using positive cols and rows');
}
pty.resize(this._fd, cols, rows);
this._cols = cols;
this._rows = rows;
}
public clear(): void {
}
private _sanitizeEnv(env: IProcessEnv): void {
// Make sure we didn't start our server from inside tmux.
delete env['TMUX'];
delete env['TMUX_PANE'];
// Make sure we didn't start our server from inside screen.
// http://web.mit.edu/gnu/doc/html/screen_20.html
delete env['STY'];
delete env['WINDOW'];
// Delete some variables that might confuse our terminal.
delete env['WINDOWID'];
delete env['TERMCAP'];
delete env['COLUMNS'];
delete env['LINES'];
}
}
interface IWriteTask {
/** The buffer being written. */
buffer: Buffer;
/** The current offset of not yet written data. */
offset: number;
}
/**
* A custom write stream that writes directly to a file descriptor with proper
* handling of backpressure and errors. This avoids some event loop exhaustion
* issues that can occur when using the standard APIs in Node.
*/
class CustomWriteStream implements IDisposable {
private readonly _writeQueue: IWriteTask[] = [];
private _writeImmediate: NodeJS.Immediate | undefined;
constructor(
private readonly _fd: number,
private readonly _encoding: BufferEncoding
) {
}
dispose(): void {
clearImmediate(this._writeImmediate);
this._writeImmediate = undefined;
}
write(data: string | Buffer): void {
// Writes are put in a queue and processed asynchronously in order to handle
// backpressure from the kernel buffer.
const buffer = typeof data === 'string'
? Buffer.from(data, this._encoding)
: Buffer.from(data);
if (buffer.byteLength !== 0) {
this._writeQueue.push({ buffer, offset: 0 });
if (this._writeQueue.length === 1) {
this._processWriteQueue();
}
}
}
private _processWriteQueue(): void {
this._writeImmediate = undefined;
if (this._writeQueue.length === 0) {
return;
}
const task = this._writeQueue[0];
// Write to the underlying file descriptor and handle it directly, rather
// than using the `net.Socket`/`tty.WriteStream` wrappers which swallow and
// mask errors like EAGAIN and can cause the thread to block indefinitely.
fs.write(this._fd, task.buffer, task.offset, (err, written) => {
if (err) {
if ('code' in err && err.code === 'EAGAIN') {
// `setImmediate` is used to yield to the event loop and re-attempt
// the write later.
this._writeImmediate = setImmediate(() => this._processWriteQueue());
} else {
// Stop processing immediately on unexpected error and log
this._writeQueue.length = 0;
console.error('Unhandled pty write error', err);
}
return;
}
task.offset += written;
if (task.offset >= task.buffer.byteLength) {
this._writeQueue.shift();
}
// Since there is more room in the kernel buffer, we can continue to write
// until we hit EAGAIN or exhaust the queue.
//
// Note that old versions of bash, like v3.2 which ships in macOS, appears
// to have a bug in its readline implementation that causes data
// corruption when writes to the pty happens too quickly. Instead of
// trying to workaround that we just accept it so that large pastes are as
// fast as possible.
// Context: https://github.com/microsoft/node-pty/issues/833
this._processWriteQueue();
});
}
}

29
node_modules/node-pty/src/utils.ts generated vendored Normal file
View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2017, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
export function assign(target: any, ...sources: any[]): any {
sources.forEach(source => Object.keys(source).forEach(key => target[key] = source[key]));
return target;
}
export function loadNativeModule(name: string): {dir: string, module: any} {
// Check build, debug, and then prebuilds.
const dirs = ['build/Release', 'build/Debug', `prebuilds/${process.platform}-${process.arch}`];
// Check relative to the parent dir for unbundled and then the current dir for bundled
const relative = ['..', '.'];
let lastError: unknown;
for (const d of dirs) {
for (const r of relative) {
const dir = `${r}/${d}/`;
try {
return { dir, module: require(`${dir}/${name}.node`) };
} catch (e) {
lastError = e;
}
}
}
throw new Error(`Failed to load native module: ${name}.node, checked: ${dirs.join(', ')}: ${lastError}`);
}

583
node_modules/node-pty/src/win/conpty.cc generated vendored Normal file
View File

@@ -0,0 +1,583 @@
/**
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*
* pty.cc:
* This file is responsible for starting processes
* with pseudo-terminal file descriptors.
*/
#define _WIN32_WINNT 0x600
#define NODE_ADDON_API_DISABLE_DEPRECATED
#include <node_api.h>
#include <assert.h>
#include <Shlwapi.h> // PathCombine, PathIsRelative
#include <sstream>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <Windows.h>
#include <strsafe.h>
#include "path_util.h"
#include "conpty.h"
// Taken from the RS5 Windows SDK, but redefined here in case we're targeting <= 17134
#ifndef PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE
#define PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE \
ProcThreadAttributeValue(22, FALSE, TRUE, FALSE)
typedef VOID* HPCON;
typedef HRESULT (__stdcall *PFNCREATEPSEUDOCONSOLE)(COORD c, HANDLE hIn, HANDLE hOut, DWORD dwFlags, HPCON* phpcon);
typedef HRESULT (__stdcall *PFNRESIZEPSEUDOCONSOLE)(HPCON hpc, COORD newSize);
typedef HRESULT (__stdcall *PFNCLEARPSEUDOCONSOLE)(HPCON hpc);
typedef void (__stdcall *PFNCLOSEPSEUDOCONSOLE)(HPCON hpc);
typedef void (__stdcall *PFNRELEASEPSEUDOCONSOLE)(HPCON hpc);
#endif
struct pty_baton {
int id;
HANDLE hIn;
HANDLE hOut;
HPCON hpc;
HANDLE hShell;
pty_baton(int _id, HANDLE _hIn, HANDLE _hOut, HPCON _hpc) : id(_id), hIn(_hIn), hOut(_hOut), hpc(_hpc) {};
};
static std::vector<std::unique_ptr<pty_baton>> ptyHandles;
static volatile LONG ptyCounter;
static pty_baton* get_pty_baton(int id) {
auto it = std::find_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
return ptyHandle->id == id;
});
if (it != ptyHandles.end()) {
return it->get();
}
return nullptr;
}
static bool remove_pty_baton(int id) {
auto it = std::remove_if(ptyHandles.begin(), ptyHandles.end(), [id](const auto& ptyHandle) {
return ptyHandle->id == id;
});
if (it != ptyHandles.end()) {
ptyHandles.erase(it);
return true;
}
return false;
}
struct ExitEvent {
int exit_code = 0;
};
void SetupExitCallback(Napi::Env env, Napi::Function cb, pty_baton* baton) {
std::thread *th = new std::thread;
// Don't use Napi::AsyncWorker which is limited by UV_THREADPOOL_SIZE.
auto tsfn = Napi::ThreadSafeFunction::New(
env,
cb, // JavaScript function called asynchronously
"SetupExitCallback_resource", // Name
0, // Unlimited queue
1, // Only one thread will use this initially
[th](Napi::Env) { // Finalizer used to clean threads up
th->join();
delete th;
});
*th = std::thread([tsfn = std::move(tsfn), baton] {
auto callback = [](Napi::Env env, Napi::Function cb, ExitEvent *exit_event) {
cb.Call({Napi::Number::New(env, exit_event->exit_code)});
delete exit_event;
};
ExitEvent *exit_event = new ExitEvent;
// Wait for process to complete.
WaitForSingleObject(baton->hShell, INFINITE);
// Get process exit code.
GetExitCodeProcess(baton->hShell, (LPDWORD)(&exit_event->exit_code));
// Clean up handles
CloseHandle(baton->hShell);
assert(remove_pty_baton(baton->id));
auto status = tsfn.BlockingCall(exit_event, callback); // In main thread
switch (status) {
case napi_closing:
break;
case napi_queue_full:
Napi::Error::Fatal("SetupExitCallback", "Queue was full");
case napi_ok:
if (tsfn.Release() != napi_ok) {
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.Release() failed");
}
break;
default:
Napi::Error::Fatal("SetupExitCallback", "ThreadSafeFunction.BlockingCall() failed");
}
});
}
Napi::Error errorWithCode(const Napi::CallbackInfo& info, const char* text) {
std::stringstream errorText;
errorText << text;
errorText << ", error code: " << GetLastError();
return Napi::Error::New(info.Env(), errorText.str());
}
// Returns a new server named pipe. It has not yet been connected.
bool createDataServerPipe(bool write,
std::wstring kind,
HANDLE* hServer,
std::wstring &name,
const std::wstring &pipeName)
{
*hServer = INVALID_HANDLE_VALUE;
name = L"\\\\.\\pipe\\" + pipeName + L"-" + kind;
const DWORD winOpenMode = PIPE_ACCESS_INBOUND | PIPE_ACCESS_OUTBOUND | FILE_FLAG_FIRST_PIPE_INSTANCE/* | FILE_FLAG_OVERLAPPED */;
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
*hServer = CreateNamedPipeW(
name.c_str(),
/*dwOpenMode=*/winOpenMode,
/*dwPipeMode=*/PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
/*nMaxInstances=*/1,
/*nOutBufferSize=*/128 * 1024,
/*nInBufferSize=*/128 * 1024,
/*nDefaultTimeOut=*/30000,
&sa);
return *hServer != INVALID_HANDLE_VALUE;
}
HANDLE LoadConptyDll(const Napi::CallbackInfo& info,
const bool useConptyDll)
{
if (!useConptyDll) {
return LoadLibraryExW(L"kernel32.dll", 0, 0);
}
wchar_t currentDir[MAX_PATH];
HMODULE hModule = GetModuleHandleA("conpty.node");
if (hModule == NULL) {
throw errorWithCode(info, "Failed to get conpty.node module handle");
}
DWORD result = GetModuleFileNameW(hModule, currentDir, MAX_PATH);
if (result == 0) {
throw errorWithCode(info, "Failed to get conpty.node module file name");
}
PathRemoveFileSpecW(currentDir);
wchar_t conptyDllPath[MAX_PATH];
PathCombineW(conptyDllPath, currentDir, L"conpty\\conpty.dll");
if (!path_util::file_exists(conptyDllPath)) {
std::wstring errorMessage = L"Cannot find conpty.dll at " + std::wstring(conptyDllPath);
std::string errorMessageStr = path_util::wstring_to_string(errorMessage);
throw errorWithCode(info, errorMessageStr.c_str());
}
return LoadLibraryW(conptyDllPath);
}
HRESULT CreateNamedPipesAndPseudoConsole(const Napi::CallbackInfo& info,
COORD size,
DWORD dwFlags,
HANDLE *phInput,
HANDLE *phOutput,
HPCON* phPC,
std::wstring& inName,
std::wstring& outName,
const std::wstring& pipeName,
const bool useConptyDll)
{
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
DWORD error = GetLastError();
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
{
PFNCREATEPSEUDOCONSOLE const pfnCreate = (PFNCREATEPSEUDOCONSOLE)GetProcAddress(
(HMODULE)hLibrary,
useConptyDll ? "ConptyCreatePseudoConsole" : "CreatePseudoConsole");
if (pfnCreate)
{
if (phPC == NULL || phInput == NULL || phOutput == NULL)
{
return E_INVALIDARG;
}
bool success = createDataServerPipe(true, L"in", phInput, inName, pipeName);
if (!success)
{
return HRESULT_FROM_WIN32(GetLastError());
}
success = createDataServerPipe(false, L"out", phOutput, outName, pipeName);
if (!success)
{
return HRESULT_FROM_WIN32(GetLastError());
}
return pfnCreate(size, *phInput, *phOutput, dwFlags, phPC);
}
else
{
// Failed to find CreatePseudoConsole in kernel32. This is likely because
// the user is not running a build of Windows that supports that API.
// We should fall back to winpty in this case.
return HRESULT_FROM_WIN32(GetLastError());
}
} else {
throw errorWithCode(info, "Failed to load conpty.dll");
}
// Failed to find kernel32. This is realy unlikely - honestly no idea how
// this is even possible to hit. But if it does happen, fall back to winpty.
return HRESULT_FROM_WIN32(GetLastError());
}
static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
Napi::Object marshal;
std::wstring inName, outName;
BOOL fSuccess = FALSE;
std::unique_ptr<wchar_t[]> mutableCommandline;
PROCESS_INFORMATION _piClient{};
if (info.Length() != 7 ||
!info[0].IsString() ||
!info[1].IsNumber() ||
!info[2].IsNumber() ||
!info[3].IsBoolean() ||
!info[4].IsString() ||
!info[5].IsBoolean() ||
!info[6].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.startProcess(file, cols, rows, debug, pipeName, inheritCursor, useConptyDll)");
}
const std::wstring filename(path_util::to_wstring(info[0].As<Napi::String>()));
const SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());
const SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
const bool debug = info[3].As<Napi::Boolean>().Value();
const std::wstring pipeName(path_util::to_wstring(info[4].As<Napi::String>()));
const bool inheritCursor = info[5].As<Napi::Boolean>().Value();
const bool useConptyDll = info[6].As<Napi::Boolean>().Value();
// use environment 'Path' variable to determine location of
// the relative path that we have recieved (e.g cmd.exe)
std::wstring shellpath;
if (::PathIsRelativeW(filename.c_str())) {
shellpath = path_util::get_shell_path(filename.c_str());
} else {
shellpath = filename;
}
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
std::string why;
why += "File not found: ";
why += path_util::wstring_to_string(shellpath);
throw Napi::Error::New(env, why);
}
HANDLE hIn, hOut;
HPCON hpc;
HRESULT hr = CreateNamedPipesAndPseudoConsole(info, {cols, rows}, inheritCursor ? 1/*PSEUDOCONSOLE_INHERIT_CURSOR*/ : 0, &hIn, &hOut, &hpc, inName, outName, pipeName, useConptyDll);
// Restore default handling of ctrl+c
SetConsoleCtrlHandler(NULL, FALSE);
// Set return values
marshal = Napi::Object::New(env);
if (SUCCEEDED(hr)) {
// We were able to instantiate a conpty
const int ptyId = InterlockedIncrement(&ptyCounter);
marshal.Set("pty", Napi::Number::New(env, ptyId));
ptyHandles.emplace_back(
std::make_unique<pty_baton>(ptyId, hIn, hOut, hpc));
} else {
throw Napi::Error::New(env, "Cannot launch conpty");
}
std::string inNameStr = path_util::wstring_to_string(inName);
if (inNameStr.empty()) {
throw Napi::Error::New(env, "Failed to initialize conpty conin");
}
std::string outNameStr = path_util::wstring_to_string(outName);
if (outNameStr.empty()) {
throw Napi::Error::New(env, "Failed to initialize conpty conout");
}
marshal.Set("fd", Napi::Number::New(env, -1));
marshal.Set("conin", Napi::String::New(env, inNameStr));
marshal.Set("conout", Napi::String::New(env, outNameStr));
return marshal;
}
static Napi::Value PtyConnect(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
// If we're working with conpty's we need to call ConnectNamedPipe here AFTER
// the Socket has attempted to connect to the other end, then actually
// spawn the process here.
std::stringstream errorText;
BOOL fSuccess = FALSE;
if (info.Length() != 6 ||
!info[0].IsNumber() ||
!info[1].IsString() ||
!info[2].IsString() ||
!info[3].IsArray() ||
!info[4].IsBoolean() ||
!info[5].IsFunction()) {
throw Napi::Error::New(env, "Usage: pty.connect(id, cmdline, cwd, env, useConptyDll, exitCallback)");
}
const int id = info[0].As<Napi::Number>().Int32Value();
const std::wstring cmdline(path_util::to_wstring(info[1].As<Napi::String>()));
const std::wstring cwd(path_util::to_wstring(info[2].As<Napi::String>()));
const Napi::Array envValues = info[3].As<Napi::Array>();
const bool useConptyDll = info[4].As<Napi::Boolean>().Value();
Napi::Function exitCallback = info[5].As<Napi::Function>();
// Fetch pty handle from ID and start process
pty_baton* handle = get_pty_baton(id);
if (!handle) {
throw Napi::Error::New(env, "Invalid pty handle");
}
// Prepare command line
std::unique_ptr<wchar_t[]> mutableCommandline = std::make_unique<wchar_t[]>(cmdline.length() + 1);
HRESULT hr = StringCchCopyW(mutableCommandline.get(), cmdline.length() + 1, cmdline.c_str());
// Prepare cwd
std::unique_ptr<wchar_t[]> mutableCwd = std::make_unique<wchar_t[]>(cwd.length() + 1);
hr = StringCchCopyW(mutableCwd.get(), cwd.length() + 1, cwd.c_str());
// Prepare environment
std::wstring envStr;
if (!envValues.IsEmpty()) {
std::wstring envBlock;
for(uint32_t i = 0; i < envValues.Length(); i++) {
envBlock += path_util::to_wstring(envValues.Get(i).As<Napi::String>());
envBlock += L'\0';
}
envBlock += L'\0';
envStr = std::move(envBlock);
}
std::vector<wchar_t> envV(envStr.cbegin(), envStr.cend());
LPWSTR envArg = envV.empty() ? nullptr : envV.data();
ConnectNamedPipe(handle->hIn, nullptr);
ConnectNamedPipe(handle->hOut, nullptr);
// Attach the pseudoconsole to the client application we're creating
STARTUPINFOEXW siEx{0};
siEx.StartupInfo.cb = sizeof(STARTUPINFOEXW);
siEx.StartupInfo.dwFlags |= STARTF_USESTDHANDLES;
siEx.StartupInfo.hStdError = nullptr;
siEx.StartupInfo.hStdInput = nullptr;
siEx.StartupInfo.hStdOutput = nullptr;
SIZE_T size = 0;
InitializeProcThreadAttributeList(NULL, 1, 0, &size);
BYTE *attrList = new BYTE[size];
siEx.lpAttributeList = reinterpret_cast<PPROC_THREAD_ATTRIBUTE_LIST>(attrList);
fSuccess = InitializeProcThreadAttributeList(siEx.lpAttributeList, 1, 0, &size);
if (!fSuccess) {
throw errorWithCode(info, "InitializeProcThreadAttributeList failed");
}
fSuccess = UpdateProcThreadAttribute(siEx.lpAttributeList,
0,
PROC_THREAD_ATTRIBUTE_PSEUDOCONSOLE,
handle->hpc,
sizeof(HPCON),
NULL,
NULL);
if (!fSuccess) {
throw errorWithCode(info, "UpdateProcThreadAttribute failed");
}
PROCESS_INFORMATION piClient{};
fSuccess = !!CreateProcessW(
nullptr,
mutableCommandline.get(),
nullptr, // lpProcessAttributes
nullptr, // lpThreadAttributes
false, // bInheritHandles VERY IMPORTANT that this is false
EXTENDED_STARTUPINFO_PRESENT | CREATE_UNICODE_ENVIRONMENT, // dwCreationFlags
envArg, // lpEnvironment
mutableCwd.get(), // lpCurrentDirectory
&siEx.StartupInfo, // lpStartupInfo
&piClient // lpProcessInformation
);
if (!fSuccess) {
throw errorWithCode(info, "Cannot create process");
}
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (useConptyDll && fLoadedDll)
{
PFNRELEASEPSEUDOCONSOLE const pfnReleasePseudoConsole = (PFNRELEASEPSEUDOCONSOLE)GetProcAddress(
(HMODULE)hLibrary, "ConptyReleasePseudoConsole");
if (pfnReleasePseudoConsole)
{
pfnReleasePseudoConsole(handle->hpc);
}
}
// Update handle
handle->hShell = piClient.hProcess;
// Close the thread handle to avoid resource leak
CloseHandle(piClient.hThread);
// Close the input read and output write handle of the pseudoconsole
CloseHandle(handle->hIn);
CloseHandle(handle->hOut);
SetupExitCallback(env, exitCallback, handle);
// Return
auto marshal = Napi::Object::New(env);
marshal.Set("pid", Napi::Number::New(env, piClient.dwProcessId));
return marshal;
}
static Napi::Value PtyResize(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 4 ||
!info[0].IsNumber() ||
!info[1].IsNumber() ||
!info[2].IsNumber() ||
!info[3].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.resize(id, cols, rows, useConptyDll)");
}
int id = info[0].As<Napi::Number>().Int32Value();
SHORT cols = static_cast<SHORT>(info[1].As<Napi::Number>().Uint32Value());
SHORT rows = static_cast<SHORT>(info[2].As<Napi::Number>().Uint32Value());
const bool useConptyDll = info[3].As<Napi::Boolean>().Value();
const pty_baton* handle = get_pty_baton(id);
if (handle != nullptr) {
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
{
PFNRESIZEPSEUDOCONSOLE const pfnResizePseudoConsole = (PFNRESIZEPSEUDOCONSOLE)GetProcAddress(
(HMODULE)hLibrary,
useConptyDll ? "ConptyResizePseudoConsole" : "ResizePseudoConsole");
if (pfnResizePseudoConsole)
{
COORD size = {cols, rows};
pfnResizePseudoConsole(handle->hpc, size);
}
}
}
return env.Undefined();
}
static Napi::Value PtyClear(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 2 ||
!info[0].IsNumber() ||
!info[1].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.clear(id, useConptyDll)");
}
int id = info[0].As<Napi::Number>().Int32Value();
const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
// This API is only supported for conpty.dll as it was introduced in a later version of Windows.
// We could hook it up to point at >= a version of Windows only, but the future is conpty.dll
// anyway.
if (!useConptyDll) {
return env.Undefined();
}
const pty_baton* handle = get_pty_baton(id);
if (handle != nullptr) {
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
{
PFNCLEARPSEUDOCONSOLE const pfnClearPseudoConsole = (PFNCLEARPSEUDOCONSOLE)GetProcAddress((HMODULE)hLibrary, "ConptyClearPseudoConsole");
if (pfnClearPseudoConsole)
{
pfnClearPseudoConsole(handle->hpc);
}
}
}
return env.Undefined();
}
static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 2 ||
!info[0].IsNumber() ||
!info[1].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.kill(id, useConptyDll)");
}
int id = info[0].As<Napi::Number>().Int32Value();
const bool useConptyDll = info[1].As<Napi::Boolean>().Value();
const pty_baton* handle = get_pty_baton(id);
if (handle != nullptr) {
HANDLE hLibrary = LoadConptyDll(info, useConptyDll);
bool fLoadedDll = hLibrary != nullptr;
if (fLoadedDll)
{
PFNCLOSEPSEUDOCONSOLE const pfnClosePseudoConsole = (PFNCLOSEPSEUDOCONSOLE)GetProcAddress(
(HMODULE)hLibrary,
useConptyDll ? "ConptyClosePseudoConsole" : "ClosePseudoConsole");
if (pfnClosePseudoConsole)
{
pfnClosePseudoConsole(handle->hpc);
}
}
if (useConptyDll) {
TerminateProcess(handle->hShell, 1);
}
}
return env.Undefined();
}
/**
* Init
*/
Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("startProcess", Napi::Function::New(env, PtyStartProcess));
exports.Set("connect", Napi::Function::New(env, PtyConnect));
exports.Set("resize", Napi::Function::New(env, PtyResize));
exports.Set("clear", Napi::Function::New(env, PtyClear));
exports.Set("kill", Napi::Function::New(env, PtyKill));
return exports;
};
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init);

41
node_modules/node-pty/src/win/conpty.h generated vendored Normal file
View File

@@ -0,0 +1,41 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// This header prototypes the Pseudoconsole symbols from conpty.lib with their original names.
// This is required because we cannot import __imp_CreatePseudoConsole from a static library
// as it doesn't produce an import lib.
// We can't use an /ALTERNATENAME trick because it seems that that name is only resolved when the
// linker cannot otherwise find the symbol.
#pragma once
#include <consoleapi.h>
#ifndef CONPTY_IMPEXP
#define CONPTY_IMPEXP __declspec(dllimport)
#endif
#ifndef CONPTY_EXPORT
#ifdef __cplusplus
#define CONPTY_EXPORT extern "C" CONPTY_IMPEXP
#else
#define CONPTY_EXPORT extern CONPTY_IMPEXP
#endif
#endif
#define PSEUDOCONSOLE_RESIZE_QUIRK (2u)
#define PSEUDOCONSOLE_PASSTHROUGH_MODE (8u)
CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsole(COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
CONPTY_EXPORT HRESULT WINAPI ConptyCreatePseudoConsoleAsUser(HANDLE hToken, COORD size, HANDLE hInput, HANDLE hOutput, DWORD dwFlags, HPCON* phPC);
CONPTY_EXPORT HRESULT WINAPI ConptyResizePseudoConsole(HPCON hPC, COORD size);
CONPTY_EXPORT HRESULT WINAPI ConptyClearPseudoConsole(HPCON hPC);
CONPTY_EXPORT HRESULT WINAPI ConptyShowHidePseudoConsole(HPCON hPC, bool show);
CONPTY_EXPORT HRESULT WINAPI ConptyReparentPseudoConsole(HPCON hPC, HWND newParent);
CONPTY_EXPORT HRESULT WINAPI ConptyReleasePseudoConsole(HPCON hPC);
CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsole(HPCON hPC);
CONPTY_EXPORT VOID WINAPI ConptyClosePseudoConsoleTimeout(HPCON hPC, DWORD dwMilliseconds);
CONPTY_EXPORT HRESULT WINAPI ConptyPackPseudoConsole(HANDLE hServerProcess, HANDLE hRef, HANDLE hSignal, HPCON* phPC);

44
node_modules/node-pty/src/win/conpty_console_list.cc generated vendored Normal file
View File

@@ -0,0 +1,44 @@
/**
* Copyright (c) 2019, Microsoft Corporation (MIT License).
*/
#define NODE_ADDON_API_DISABLE_DEPRECATED
#include <napi.h>
#include <windows.h>
static Napi::Value ApiConsoleProcessList(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
if (info.Length() != 1 ||
!info[0].IsNumber()) {
throw Napi::Error::New(env, "Usage: getConsoleProcessList(shellPid)");
}
const DWORD pid = info[0].As<Napi::Number>().Uint32Value();
if (!FreeConsole()) {
throw Napi::Error::New(env, "FreeConsole failed");
}
if (!AttachConsole(pid)) {
throw Napi::Error::New(env, "AttachConsole failed");
}
auto processList = std::vector<DWORD>(64);
auto processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
if (processList.size() < processCount) {
processList.resize(processCount);
processCount = GetConsoleProcessList(&processList[0], static_cast<DWORD>(processList.size()));
}
FreeConsole();
Napi::Array result = Napi::Array::New(env);
for (DWORD i = 0; i < processCount; i++) {
result.Set(i, Napi::Number::New(env, processList[i]));
}
return result;
}
Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("getConsoleProcessList", Napi::Function::New(env, ApiConsoleProcessList));
return exports;
};
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init);

95
node_modules/node-pty/src/win/path_util.cc generated vendored Normal file
View File

@@ -0,0 +1,95 @@
/**
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
#include <stdexcept>
#include <Shlwapi.h> // PathCombine
#include <Windows.h>
#include "path_util.h"
namespace path_util {
std::wstring to_wstring(const Napi::String& str) {
const std::u16string & u16 = str.Utf16Value();
return std::wstring(u16.begin(), u16.end());
}
std::string wstring_to_string(const std::wstring &wide_string) {
if (wide_string.empty()) {
return "";
}
const auto size_needed = WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0), (int)wide_string.size(), nullptr, 0, nullptr, nullptr);
if (size_needed <= 0) {
return "";
}
std::string result(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0, &wide_string.at(0), (int)wide_string.size(), &result.at(0), size_needed, nullptr, nullptr);
return result;
}
const char* from_wstring(const wchar_t* wstr) {
int bufferSize = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
if (bufferSize <= 0) {
return "";
}
char *output = new char[bufferSize];
int status = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, output, bufferSize, NULL, NULL);
if (status == 0) {
return "";
}
return output;
}
bool file_exists(std::wstring filename) {
DWORD attr = ::GetFileAttributesW(filename.c_str());
if (attr == INVALID_FILE_ATTRIBUTES || (attr & FILE_ATTRIBUTE_DIRECTORY)) {
return false;
}
return true;
}
// cmd.exe -> C:\Windows\system32\cmd.exe
std::wstring get_shell_path(std::wstring filename) {
std::wstring shellpath;
if (file_exists(filename)) {
return shellpath;
}
wchar_t* buffer_ = new wchar_t[MAX_ENV];
int read = ::GetEnvironmentVariableW(L"Path", buffer_, MAX_ENV);
if (read) {
std::wstring delimiter = L";";
size_t pos = 0;
std::vector<std::wstring> paths;
std::wstring buffer(buffer_);
while ((pos = buffer.find(delimiter)) != std::wstring::npos) {
paths.push_back(buffer.substr(0, pos));
buffer.erase(0, pos + delimiter.length());
}
const wchar_t *filename_ = filename.c_str();
for (size_t i = 0; i < paths.size(); ++i) {
std::wstring path = paths[i];
wchar_t searchPath[MAX_PATH];
::PathCombineW(searchPath, const_cast<wchar_t*>(path.c_str()), filename_);
if (searchPath == NULL) {
continue;
}
if (file_exists(searchPath)) {
shellpath = searchPath;
break;
}
}
}
delete[] buffer_;
return shellpath;
}
} // namespace path_util

26
node_modules/node-pty/src/win/path_util.h generated vendored Normal file
View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
#ifndef NODE_PTY_PATH_UTIL_H_
#define NODE_PTY_PATH_UTIL_H_
#define NODE_ADDON_API_DISABLE_DEPRECATED
#include <napi.h>
#include <string>
#define MAX_ENV 65536
namespace path_util {
std::wstring to_wstring(const Napi::String& str);
std::string wstring_to_string(const std::wstring &wide_string);
const char* from_wstring(const wchar_t* wstr);
bool file_exists(std::wstring filename);
std::wstring get_shell_path(std::wstring filename);
} // namespace path_util
#endif // NODE_PTY_PATH_UTIL_H_

333
node_modules/node-pty/src/win/winpty.cc generated vendored Normal file
View File

@@ -0,0 +1,333 @@
/**
* Copyright (c) 2013-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*
* pty.cc:
* This file is responsible for starting processes
* with pseudo-terminal file descriptors.
*/
#define NODE_ADDON_API_DISABLE_DEPRECATED
#include <napi.h>
#include <iostream>
#include <assert.h>
#include <map>
#include <Shlwapi.h> // PathCombine, PathIsRelative
#include <sstream>
#include <stdlib.h>
#include <string.h>
#include <string>
#include <vector>
#include <winpty.h>
#include "path_util.h"
/**
* Misc
*/
#define WINPTY_DBG_VARIABLE TEXT("WINPTYDBG")
/**
* winpty
*/
static std::vector<winpty_t *> ptyHandles;
static volatile LONG ptyCounter;
/**
* Helpers
*/
/** Keeps track of the handles created by PtyStartProcess */
static std::map<DWORD, HANDLE> createdHandles;
static winpty_t *get_pipe_handle(DWORD pid) {
for (size_t i = 0; i < ptyHandles.size(); ++i) {
winpty_t *ptyHandle = ptyHandles[i];
HANDLE current = winpty_agent_process(ptyHandle);
if (GetProcessId(current) == pid) {
return ptyHandle;
}
}
return nullptr;
}
static bool remove_pipe_handle(DWORD pid) {
for (size_t i = 0; i < ptyHandles.size(); ++i) {
winpty_t *ptyHandle = ptyHandles[i];
HANDLE current = winpty_agent_process(ptyHandle);
if (GetProcessId(current) == pid) {
winpty_free(ptyHandle);
ptyHandles.erase(ptyHandles.begin() + i);
ptyHandle = nullptr;
return true;
}
}
return false;
}
Napi::Error error_with_winpty_msg(const char *generalMsg, winpty_error_ptr_t error_ptr, Napi::Env env) {
std::string why;
why += generalMsg;
why += ": ";
why += path_util::wstring_to_string(winpty_error_msg(error_ptr));
winpty_error_free(error_ptr);
return Napi::Error::New(env, why);
}
static Napi::Value PtyGetExitCode(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 1 ||
!info[0].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.getExitCode(pid)");
}
DWORD pid = info[0].As<Napi::Number>().Uint32Value();
HANDLE handle = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pid);
if (handle == NULL) {
return Napi::Number::New(env, -1);
}
DWORD exitCode = 0;
BOOL success = GetExitCodeProcess(handle, &exitCode);
if (success == FALSE) {
exitCode = -1;
}
CloseHandle(handle);
return Napi::Number::New(env, exitCode);
}
static Napi::Value PtyGetProcessList(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 1 ||
!info[0].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.getProcessList(pid)");
}
DWORD pid = info[0].As<Napi::Number>().Uint32Value();
winpty_t *pc = get_pipe_handle(pid);
if (pc == nullptr) {
return Napi::Number::New(env, 0);
}
int processList[64];
const int processCount = 64;
int actualCount = winpty_get_console_process_list(pc, processList, processCount, nullptr);
if (actualCount <= 0) {
return Napi::Number::New(env, 0);
}
Napi::Array result = Napi::Array::New(env, actualCount);
for (int i = 0; i < actualCount; i++) {
result.Set(i, Napi::Number::New(env, processList[i]));
}
return result;
}
static Napi::Value PtyStartProcess(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 7 ||
!info[0].IsString() ||
!info[1].IsString() ||
!info[2].IsArray() ||
!info[3].IsString() ||
!info[4].IsNumber() ||
!info[5].IsNumber() ||
!info[6].IsBoolean()) {
throw Napi::Error::New(env, "Usage: pty.startProcess(file, cmdline, env, cwd, cols, rows, debug)");
}
std::wstring filename(path_util::to_wstring(info[0].As<Napi::String>()));
std::wstring cmdline(path_util::to_wstring(info[1].As<Napi::String>()));
std::wstring cwd(path_util::to_wstring(info[3].As<Napi::String>()));
// create environment block
std::wstring envStr;
const Napi::Array envValues = info[2].As<Napi::Array>();
if (!envValues.IsEmpty()) {
std::wstring envBlock;
for(uint32_t i = 0; i < envValues.Length(); i++) {
envBlock += path_util::to_wstring(envValues.Get(i).As<Napi::String>());
envBlock += L'\0';
}
envStr = std::move(envBlock);
}
// use environment 'Path' variable to determine location of
// the relative path that we have recieved (e.g cmd.exe)
std::wstring shellpath;
if (::PathIsRelativeW(filename.c_str())) {
shellpath = path_util::get_shell_path(filename);
} else {
shellpath = filename;
}
if (shellpath.empty() || !path_util::file_exists(shellpath)) {
std::string why;
why += "File not found: ";
why += path_util::wstring_to_string(shellpath);
throw Napi::Error::New(env, why);
}
int cols = info[4].As<Napi::Number>().Int32Value();
int rows = info[5].As<Napi::Number>().Int32Value();
bool debug = info[6].As<Napi::Boolean>().Value();
// Enable/disable debugging
SetEnvironmentVariable(WINPTY_DBG_VARIABLE, debug ? "1" : NULL); // NULL = deletes variable
// Create winpty config
winpty_error_ptr_t error_ptr = nullptr;
winpty_config_t* winpty_config = winpty_config_new(0, &error_ptr);
if (winpty_config == nullptr) {
throw error_with_winpty_msg("Error creating WinPTY config", error_ptr, env);
}
winpty_error_free(error_ptr);
// Set pty size on config
winpty_config_set_initial_size(winpty_config, cols, rows);
// Start the pty agent
winpty_t *pc = winpty_open(winpty_config, &error_ptr);
winpty_config_free(winpty_config);
if (pc == nullptr) {
throw error_with_winpty_msg("Error launching WinPTY agent", error_ptr, env);
}
winpty_error_free(error_ptr);
// Create winpty spawn config
winpty_spawn_config_t* config = winpty_spawn_config_new(WINPTY_SPAWN_FLAG_AUTO_SHUTDOWN, shellpath.c_str(), cmdline.c_str(), cwd.c_str(), envStr.c_str(), &error_ptr);
if (config == nullptr) {
winpty_free(pc);
throw error_with_winpty_msg("Error creating WinPTY spawn config", error_ptr, env);
}
winpty_error_free(error_ptr);
// Spawn the new process
HANDLE handle = nullptr;
BOOL spawnSuccess = winpty_spawn(pc, config, &handle, nullptr, nullptr, &error_ptr);
winpty_spawn_config_free(config);
if (!spawnSuccess) {
if (handle) {
CloseHandle(handle);
}
winpty_free(pc);
throw error_with_winpty_msg("Unable to start terminal process", error_ptr, env);
}
winpty_error_free(error_ptr);
LPCWSTR coninPipeName = winpty_conin_name(pc);
std::string coninPipeNameStr(path_util::from_wstring(coninPipeName));
if (coninPipeNameStr.empty()) {
CloseHandle(handle);
winpty_free(pc);
throw Napi::Error::New(env, "Failed to initialize winpty conin");
}
LPCWSTR conoutPipeName = winpty_conout_name(pc);
std::string conoutPipeNameStr(path_util::from_wstring(conoutPipeName));
if (conoutPipeNameStr.empty()) {
CloseHandle(handle);
winpty_free(pc);
throw Napi::Error::New(env, "Failed to initialize winpty conout");
}
DWORD innerPid = GetProcessId(handle);
if (createdHandles[innerPid]) {
CloseHandle(handle);
winpty_free(pc);
std::stringstream why;
why << "There is already a process with innerPid " << innerPid;
throw Napi::Error::New(env, why.str());
}
createdHandles[innerPid] = handle;
// Save pty struct for later use
ptyHandles.push_back(pc);
DWORD pid = GetProcessId(winpty_agent_process(pc));
Napi::Object marshal = Napi::Object::New(env);
marshal.Set("innerPid", Napi::Number::New(env, (int)innerPid));
marshal.Set("pid", Napi::Number::New(env, (int)pid));
marshal.Set("pty", Napi::Number::New(env, InterlockedIncrement(&ptyCounter)));
marshal.Set("fd", Napi::Number::New(env, -1));
marshal.Set("conin", Napi::String::New(env, coninPipeNameStr));
marshal.Set("conout", Napi::String::New(env, conoutPipeNameStr));
return marshal;
}
static Napi::Value PtyResize(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 3 ||
!info[0].IsNumber() ||
!info[1].IsNumber() ||
!info[2].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.resize(pid, cols, rows)");
}
DWORD pid = info[0].As<Napi::Number>().Uint32Value();
int cols = info[1].As<Napi::Number>().Int32Value();
int rows = info[2].As<Napi::Number>().Int32Value();
winpty_t *pc = get_pipe_handle(pid);
if (pc == nullptr) {
throw Napi::Error::New(env, "The pty doesn't appear to exist");
}
BOOL success = winpty_set_size(pc, cols, rows, nullptr);
if (!success) {
throw Napi::Error::New(env, "The pty could not be resized");
}
return env.Undefined();
}
static Napi::Value PtyKill(const Napi::CallbackInfo& info) {
Napi::Env env(info.Env());
Napi::HandleScope scope(env);
if (info.Length() != 2 ||
!info[0].IsNumber() ||
!info[1].IsNumber()) {
throw Napi::Error::New(env, "Usage: pty.kill(pid, innerPid)");
}
DWORD pid = info[0].As<Napi::Number>().Uint32Value();
DWORD innerPid = info[1].As<Napi::Number>().Uint32Value();
winpty_t *pc = get_pipe_handle(pid);
if (pc == nullptr) {
throw Napi::Error::New(env, "Pty seems to have been killed already");
}
assert(remove_pipe_handle(pid));
HANDLE innerPidHandle = createdHandles[innerPid];
createdHandles.erase(innerPid);
CloseHandle(innerPidHandle);
return env.Undefined();
}
/**
* Init
*/
Napi::Object init(Napi::Env env, Napi::Object exports) {
exports.Set("startProcess", Napi::Function::New(env, PtyStartProcess));
exports.Set("resize", Napi::Function::New(env, PtyResize));
exports.Set("kill", Napi::Function::New(env, PtyKill));
exports.Set("getExitCode", Napi::Function::New(env, PtyGetExitCode));
exports.Set("getProcessList", Napi::Function::New(env, PtyGetProcessList));
return exports;
};
NODE_API_MODULE(NODE_GYP_MODULE_NAME, init);

82
node_modules/node-pty/src/windowsConoutConnection.ts generated vendored Normal file
View File

@@ -0,0 +1,82 @@
/**
* Copyright (c) 2020, Microsoft Corporation (MIT License).
*/
import { Worker } from 'worker_threads';
import { Socket } from 'net';
import { IDisposable } from './types';
import { IWorkerData, ConoutWorkerMessage, getWorkerPipeName } from './shared/conout';
import { join } from 'path';
import { IEvent, EventEmitter2 } from './eventEmitter2';
/**
* The amount of time to wait for additional data after the conpty shell process has exited before
* shutting down the worker and sockets. The timer will be reset if a new data event comes in after
* the timer has started.
*/
const FLUSH_DATA_INTERVAL = 1000;
/**
* Connects to and manages the lifecycle of the conout socket. This socket must be drained on
* another thread in order to avoid deadlocks where Conpty waits for the out socket to drain
* when `ClosePseudoConsole` is called. This happens when data is being written to the terminal when
* the pty is closed.
*
* See also:
* - https://github.com/microsoft/node-pty/issues/375
* - https://github.com/microsoft/vscode/issues/76548
* - https://github.com/microsoft/terminal/issues/1810
* - https://docs.microsoft.com/en-us/windows/console/closepseudoconsole
*/
export class ConoutConnection implements IDisposable {
private _worker: Worker;
private _drainTimeout: NodeJS.Timeout | undefined;
private _isDisposed: boolean = false;
private _onReady = new EventEmitter2<void>();
public get onReady(): IEvent<void> { return this._onReady.event; }
constructor(
private _conoutPipeName: string,
private _useConptyDll: boolean
) {
const workerData: IWorkerData = {
conoutPipeName: _conoutPipeName
};
const scriptPath = __dirname.replace('node_modules.asar', 'node_modules.asar.unpacked');
this._worker = new Worker(join(scriptPath, 'worker/conoutSocketWorker.js'), { workerData });
this._worker.on('message', (message: ConoutWorkerMessage) => {
switch (message) {
case ConoutWorkerMessage.READY:
this._onReady.fire();
return;
default:
console.warn('Unexpected ConoutWorkerMessage', message);
}
});
}
dispose(): void {
if (!this._useConptyDll && this._isDisposed) {
return;
}
this._isDisposed = true;
// Drain all data from the socket before closing
this._drainDataAndClose();
}
connectSocket(socket: Socket): void {
socket.connect(getWorkerPipeName(this._conoutPipeName));
}
private _drainDataAndClose(): void {
if (this._drainTimeout) {
clearTimeout(this._drainTimeout);
}
this._drainTimeout = setTimeout(() => this._destroySocket(), FLUSH_DATA_INTERVAL);
}
private async _destroySocket(): Promise<void> {
await this._worker.terminate();
}
}

94
node_modules/node-pty/src/windowsPtyAgent.test.ts generated vendored Normal file
View File

@@ -0,0 +1,94 @@
/**
* Copyright (c) 2017, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import * as assert from 'assert';
import { argsToCommandLine } from './windowsPtyAgent';
function check(file: string, args: string | string[], expected: string): void {
assert.equal(argsToCommandLine(file, args), expected);
}
if (process.platform === 'win32') {
describe('argsToCommandLine', () => {
describe('Plain strings', () => {
it('doesn\'t quote plain string', () => {
check('asdf', [], 'asdf');
});
it('doesn\'t escape backslashes', () => {
check('\\asdf\\qwer\\', [], '\\asdf\\qwer\\');
});
it('doesn\'t escape multiple backslashes', () => {
check('asdf\\\\qwer', [], 'asdf\\\\qwer');
});
it('adds backslashes before quotes', () => {
check('"asdf"qwer"', [], '\\"asdf\\"qwer\\"');
});
it('escapes backslashes before quotes', () => {
check('asdf\\"qwer', [], 'asdf\\\\\\"qwer');
});
});
describe('Quoted strings', () => {
it('quotes string with spaces', () => {
check('asdf qwer', [], '"asdf qwer"');
});
it('quotes empty string', () => {
check('', [], '""');
});
it('quotes string with tabs', () => {
check('asdf\tqwer', [], '"asdf\tqwer"');
});
it('escapes only the last backslash', () => {
check('\\asdf \\qwer\\', [], '"\\asdf \\qwer\\\\"');
});
it('doesn\'t escape multiple backslashes', () => {
check('asdf \\\\qwer', [], '"asdf \\\\qwer"');
});
it('escapes backslashes before quotes', () => {
check('asdf \\"qwer', [], '"asdf \\\\\\"qwer"');
});
it('escapes multiple backslashes at the end', () => {
check('asdf qwer\\\\', [], '"asdf qwer\\\\\\\\"');
});
});
describe('Multiple arguments', () => {
it('joins arguments with spaces', () => {
check('asdf', ['qwer zxcv', '', '"'], 'asdf "qwer zxcv" "" \\"');
});
it('array argument all in quotes', () => {
check('asdf', ['"surounded by quotes"'], 'asdf \\"surounded by quotes\\"');
});
it('array argument quotes in the middle', () => {
check('asdf', ['quotes "in the" middle'], 'asdf "quotes \\"in the\\" middle"');
});
it('array argument quotes near start', () => {
check('asdf', ['"quotes" near start'], 'asdf "\\"quotes\\" near start"');
});
it('array argument quotes near end', () => {
check('asdf', ['quotes "near end"'], 'asdf "quotes \\"near end\\""');
});
});
describe('Args as CommandLine', () => {
it('should handle empty string', () => {
check('file', '', 'file');
});
it('should not change args', () => {
check('file', 'foo bar baz', 'file foo bar baz');
check('file', 'foo \\ba"r \baz', 'file foo \\ba"r \baz');
});
});
describe('Real-world cases', () => {
it('quotes within quotes', () => {
check('cmd.exe', ['/c', 'powershell -noexit -command \'Set-location \"C:\\user\"\''], 'cmd.exe /c "powershell -noexit -command \'Set-location \\\"C:\\user\\"\'"');
});
it('space within quotes', () => {
check('cmd.exe', ['/k', '"C:\\Users\\alros\\Desktop\\test script.bat"'], 'cmd.exe /k \\"C:\\Users\\alros\\Desktop\\test script.bat\\"');
});
});
});
}

321
node_modules/node-pty/src/windowsPtyAgent.ts generated vendored Normal file
View File

@@ -0,0 +1,321 @@
/**
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { fork } from 'child_process';
import { Socket } from 'net';
import { ArgvOrCommandLine } from './types';
import { ConoutConnection } from './windowsConoutConnection';
import { loadNativeModule } from './utils';
let conptyNative: IConptyNative;
let winptyNative: IWinptyNative;
/**
* The amount of time to wait for additional data after the conpty shell process has exited before
* shutting down the socket. The timer will be reset if a new data event comes in after the timer
* has started.
*/
const FLUSH_DATA_INTERVAL = 1000;
/**
* This agent sits between the WindowsTerminal class and provides a common interface for both conpty
* and winpty.
*/
export class WindowsPtyAgent {
private _inSocket: Socket;
private _outSocket: Socket;
private _pid: number = 0;
private _innerPid: number = 0;
private _closeTimeout: NodeJS.Timer | undefined;
private _exitCode: number | undefined;
private _conoutSocketWorker: ConoutConnection;
private _fd: any;
private _pty: number;
private _ptyNative: IConptyNative | IWinptyNative;
public get inSocket(): Socket { return this._inSocket; }
public get outSocket(): Socket { return this._outSocket; }
public get fd(): any { return this._fd; }
public get innerPid(): number { return this._innerPid; }
public get pty(): number { return this._pty; }
constructor(
file: string,
args: ArgvOrCommandLine,
env: string[],
cwd: string,
cols: number,
rows: number,
debug: boolean,
private _useConpty: boolean | undefined,
private _useConptyDll: boolean = false,
conptyInheritCursor: boolean = false
) {
if (this._useConpty === undefined || this._useConpty === true) {
this._useConpty = this._getWindowsBuildNumber() >= 18309;
}
if (this._useConpty) {
if (!conptyNative) {
conptyNative = loadNativeModule('conpty').module;
}
} else {
if (!winptyNative) {
winptyNative = loadNativeModule('pty').module;
}
}
this._ptyNative = this._useConpty ? conptyNative : winptyNative;
// Sanitize input variable.
cwd = path.resolve(cwd);
// Compose command line
const commandLine = argsToCommandLine(file, args);
// Open pty session.
let term: IConptyProcess | IWinptyProcess;
if (this._useConpty) {
term = (this._ptyNative as IConptyNative).startProcess(file, cols, rows, debug, this._generatePipeName(), conptyInheritCursor, this._useConptyDll);
} else {
term = (this._ptyNative as IWinptyNative).startProcess(file, commandLine, env, cwd, cols, rows, debug);
this._pid = (term as IWinptyProcess).pid;
this._innerPid = (term as IWinptyProcess).innerPid;
}
// Not available on windows.
this._fd = term.fd;
// Generated incremental number that has no real purpose besides using it
// as a terminal id.
this._pty = term.pty;
// Create terminal pipe IPC channel and forward to a local unix socket.
this._outSocket = new Socket();
this._outSocket.setEncoding('utf8');
// The conout socket must be ready out on another thread to avoid deadlocks
this._conoutSocketWorker = new ConoutConnection(term.conout, this._useConptyDll);
this._conoutSocketWorker.onReady(() => {
this._conoutSocketWorker.connectSocket(this._outSocket);
});
this._outSocket.on('connect', () => {
this._outSocket.emit('ready_datapipe');
});
const inSocketFD = fs.openSync(term.conin, 'w');
this._inSocket = new Socket({
fd: inSocketFD,
readable: false,
writable: true
});
this._inSocket.setEncoding('utf8');
if (this._useConpty) {
const connect = (this._ptyNative as IConptyNative).connect(this._pty, commandLine, cwd, env, this._useConptyDll, c => this._$onProcessExit(c));
this._innerPid = connect.pid;
}
}
public resize(cols: number, rows: number): void {
if (this._useConpty) {
if (this._exitCode !== undefined) {
throw new Error('Cannot resize a pty that has already exited');
}
(this._ptyNative as IConptyNative).resize(this._pty, cols, rows, this._useConptyDll);
return;
}
(this._ptyNative as IWinptyNative).resize(this._pid, cols, rows);
}
public clear(): void {
if (this._useConpty) {
(this._ptyNative as IConptyNative).clear(this._pty, this._useConptyDll);
}
}
public kill(): void {
// Tell the agent to kill the pty, this releases handles to the process
if (this._useConpty) {
if (!this._useConptyDll) {
this._inSocket.readable = false;
this._outSocket.readable = false;
this._getConsoleProcessList().then(consoleProcessList => {
consoleProcessList.forEach((pid: number) => {
try {
process.kill(pid);
} catch (e) {
// Ignore if process cannot be found (kill ESRCH error)
}
});
});
(this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll);
this._conoutSocketWorker.dispose();
} else {
// Close the input write handle to signal the end of session.
this._inSocket.destroy();
(this._ptyNative as IConptyNative).kill(this._pty, this._useConptyDll);
this._outSocket.on('data', () => {
this._conoutSocketWorker.dispose();
});
}
} else {
// Because pty.kill closes the handle, it will kill most processes by itself.
// Process IDs can be reused as soon as all handles to them are
// dropped, so we want to immediately kill the entire console process list.
// If we do not force kill all processes here, node servers in particular
// seem to become detached and remain running (see
// Microsoft/vscode#26807).
const processList: number[] = (this._ptyNative as IWinptyNative).getProcessList(this._pid);
(this._ptyNative as IWinptyNative).kill(this._pid, this._innerPid);
processList.forEach(pid => {
try {
process.kill(pid);
} catch (e) {
// Ignore if process cannot be found (kill ESRCH error)
}
});
}
}
private _getConsoleProcessList(): Promise<number[]> {
return new Promise<number[]>(resolve => {
const agent = fork(path.join(__dirname, 'conpty_console_list_agent'), [ this._innerPid.toString() ]);
agent.on('message', message => {
clearTimeout(timeout);
resolve(message.consoleProcessList);
});
const timeout = setTimeout(() => {
// Something went wrong, just send back the shell PID
agent.kill();
resolve([ this._innerPid ]);
}, 5000);
});
}
public get exitCode(): number | undefined {
if (this._useConpty) {
return this._exitCode;
}
const winptyExitCode = (this._ptyNative as IWinptyNative).getExitCode(this._innerPid);
return winptyExitCode === -1 ? undefined : winptyExitCode;
}
private _getWindowsBuildNumber(): number {
const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release());
let buildNumber: number = 0;
if (osVersion && osVersion.length === 4) {
buildNumber = parseInt(osVersion[3]);
}
return buildNumber;
}
private _generatePipeName(): string {
return `conpty-${Math.random() * 10000000}`;
}
/**
* Triggered from the native side when a contpy process exits.
*/
private _$onProcessExit(exitCode: number): void {
this._exitCode = exitCode;
if (!this._useConptyDll) {
this._flushDataAndCleanUp();
this._outSocket.on('data', () => this._flushDataAndCleanUp());
}
}
private _flushDataAndCleanUp(): void {
if (this._useConptyDll) {
return;
}
if (this._closeTimeout) {
clearTimeout(this._closeTimeout);
}
this._closeTimeout = setTimeout(() => this._cleanUpProcess(), FLUSH_DATA_INTERVAL);
}
private _cleanUpProcess(): void {
if (this._useConptyDll) {
return;
}
this._inSocket.readable = false;
this._outSocket.readable = false;
this._outSocket.destroy();
}
}
// Convert argc/argv into a Win32 command-line following the escaping convention
// documented on MSDN (e.g. see CommandLineToArgvW documentation). Copied from
// winpty project.
export function argsToCommandLine(file: string, args: ArgvOrCommandLine): string {
if (isCommandLine(args)) {
if (args.length === 0) {
return file;
}
return `${argsToCommandLine(file, [])} ${args}`;
}
const argv = [file];
Array.prototype.push.apply(argv, args);
let result = '';
for (let argIndex = 0; argIndex < argv.length; argIndex++) {
if (argIndex > 0) {
result += ' ';
}
const arg = argv[argIndex];
// if it is empty or it contains whitespace and is not already quoted
const hasLopsidedEnclosingQuote = xOr((arg[0] !== '"'), (arg[arg.length - 1] !== '"'));
const hasNoEnclosingQuotes = ((arg[0] !== '"') && (arg[arg.length - 1] !== '"'));
const quote =
arg === '' ||
(arg.indexOf(' ') !== -1 ||
arg.indexOf('\t') !== -1) &&
((arg.length > 1) &&
(hasLopsidedEnclosingQuote || hasNoEnclosingQuotes));
if (quote) {
result += '\"';
}
let bsCount = 0;
for (let i = 0; i < arg.length; i++) {
const p = arg[i];
if (p === '\\') {
bsCount++;
} else if (p === '"') {
result += repeatText('\\', bsCount * 2 + 1);
result += '"';
bsCount = 0;
} else {
result += repeatText('\\', bsCount);
bsCount = 0;
result += p;
}
}
if (quote) {
result += repeatText('\\', bsCount * 2);
result += '\"';
} else {
result += repeatText('\\', bsCount);
}
}
return result;
}
function isCommandLine(args: ArgvOrCommandLine): args is string {
return typeof args === 'string';
}
function repeatText(text: string, count: number): string {
let result = '';
for (let i = 0; i < count; i++) {
result += text;
}
return result;
}
function xOr(arg1: boolean, arg2: boolean): boolean {
return ((arg1 && !arg2) || (!arg1 && arg2));
}

229
node_modules/node-pty/src/windowsTerminal.test.ts generated vendored Normal file
View File

@@ -0,0 +1,229 @@
/**
* Copyright (c) 2017, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import * as fs from 'fs';
import * as assert from 'assert';
import { WindowsTerminal } from './windowsTerminal';
import * as path from 'path';
import * as psList from 'ps-list';
interface IProcessState {
// Whether the PID must exist or must not exist
[pid: number]: boolean;
}
interface IWindowsProcessTreeResult {
name: string;
pid: number;
}
function pollForProcessState(desiredState: IProcessState, intervalMs: number = 100, timeoutMs: number = 2000): Promise<void> {
return new Promise<void>(resolve => {
let tries = 0;
const interval = setInterval(() => {
psList({ all: true }).then(ps => {
let success = true;
const pids = Object.keys(desiredState).map(k => parseInt(k, 10));
console.log('expected pids', JSON.stringify(pids));
pids.forEach(pid => {
if (desiredState[pid]) {
if (!ps.some(p => p.pid === pid)) {
console.log(`pid ${pid} does not exist`);
success = false;
}
} else {
if (ps.some(p => p.pid === pid)) {
console.log(`pid ${pid} still exists`);
success = false;
}
}
});
if (success) {
clearInterval(interval);
resolve();
return;
}
tries++;
if (tries * intervalMs >= timeoutMs) {
clearInterval(interval);
const processListing = pids.map(k => `${k}: ${desiredState[k]}`).join('\n');
assert.fail(`Bad process state, expected:\n${processListing}`);
resolve();
}
});
}, intervalMs);
});
}
function pollForProcessTreeSize(pid: number, size: number, intervalMs: number = 100, timeoutMs: number = 2000): Promise<IWindowsProcessTreeResult[]> {
return new Promise<IWindowsProcessTreeResult[]>(resolve => {
let tries = 0;
const interval = setInterval(() => {
psList({ all: true }).then(ps => {
const openList: IWindowsProcessTreeResult[] = [];
openList.push(ps.filter(p => p.pid === pid).map(p => {
return { name: p.name, pid: p.pid };
})[0]);
const list: IWindowsProcessTreeResult[] = [];
while (openList.length) {
const current = openList.shift()!;
ps.filter(p => p.ppid === current.pid).map(p => {
return { name: p.name, pid: p.pid };
}).forEach(p => openList.push(p));
list.push(current);
}
console.log('list', JSON.stringify(list));
const success = list.length === size;
if (success) {
clearInterval(interval);
resolve(list);
return;
}
tries++;
if (tries * intervalMs >= timeoutMs) {
clearInterval(interval);
assert.fail(`Bad process state, expected: ${size}, actual: ${list.length}`);
}
});
}, intervalMs);
});
}
if (process.platform === 'win32') {
[[false, false], [true, false], [true, true]].forEach(([useConpty, useConptyDll]) => {
describe(`WindowsTerminal (useConpty = ${useConpty}, useConptyDll = ${useConptyDll})`, () => {
describe('kill', () => {
it('should not crash parent process', function (done) {
this.timeout(20000);
const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
term.on('exit', () => done());
term.kill();
});
it('should kill the process tree', function (done: Mocha.Done): void {
this.timeout(20000);
const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
// Start sub-processes
term.write('powershell.exe\r');
term.write('node.exe\r');
console.log('start poll for tree size');
pollForProcessTreeSize(term.pid, 3, 500, 5000).then(list => {
assert.strictEqual(list[0].name.toLowerCase(), 'cmd.exe');
assert.strictEqual(list[1].name.toLowerCase(), 'powershell.exe');
assert.strictEqual(list[2].name.toLowerCase(), 'node.exe');
term.kill();
const desiredState: IProcessState = {};
desiredState[list[0].pid] = false;
desiredState[list[1].pid] = false;
desiredState[list[2].pid] = false;
term.on('exit', () => {
pollForProcessState(desiredState, 1000, 5000).then(() => {
done();
});
});
});
});
});
describe('resize', () => {
it('should throw a non-native exception when resizing an invalid value', function(done) {
this.timeout(20000);
const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
assert.throws(() => term.resize(-1, -1));
assert.throws(() => term.resize(0, 0));
assert.doesNotThrow(() => term.resize(1, 1));
term.on('exit', () => {
done();
});
term.kill();
});
it('should throw a non-native exception when resizing a killed terminal', function(done) {
this.timeout(20000);
const term = new WindowsTerminal('cmd.exe', [], { useConpty, useConptyDll });
(<any>term)._defer(() => {
term.once('exit', () => {
assert.throws(() => term.resize(1, 1));
done();
});
term.destroy();
});
});
});
describe('Args as CommandLine', () => {
it('should not fail running a file containing a space in the path', function (done) {
this.timeout(10000);
const spaceFolder = path.resolve(__dirname, '..', 'fixtures', 'space folder');
if (!fs.existsSync(spaceFolder)) {
fs.mkdirSync(spaceFolder);
}
const cmdCopiedPath = path.resolve(spaceFolder, 'cmd.exe');
const data = fs.readFileSync(`${process.env.windir}\\System32\\cmd.exe`);
fs.writeFileSync(cmdCopiedPath, data);
if (!fs.existsSync(cmdCopiedPath)) {
// Skip test if git bash isn't installed
return;
}
const term = new WindowsTerminal(cmdCopiedPath, '/c echo "hello world"', { useConpty, useConptyDll });
let result = '';
term.on('data', (data) => {
result += data;
});
term.on('exit', () => {
assert.ok(result.indexOf('hello world') >= 1);
done();
});
});
});
describe('env', () => {
it('should set environment variables of the shell', function (done) {
this.timeout(10000);
const term = new WindowsTerminal('cmd.exe', '/C echo %FOO%', { useConpty, useConptyDll, env: { FOO: 'BAR' }});
let result = '';
term.on('data', (data) => {
result += data;
});
term.on('exit', () => {
assert.ok(result.indexOf('BAR') >= 0);
done();
});
});
});
describe('On close', () => {
it('should return process zero exit codes', function (done) {
this.timeout(10000);
const term = new WindowsTerminal('cmd.exe', '/C exit', { useConpty, useConptyDll });
term.on('exit', (code) => {
assert.strictEqual(code, 0);
done();
});
});
it('should return process non-zero exit codes', function (done) {
this.timeout(10000);
const term = new WindowsTerminal('cmd.exe', '/C exit 2', { useConpty, useConptyDll });
term.on('exit', (code) => {
assert.strictEqual(code, 2);
done();
});
});
});
describe('Write', () => {
it('should accept input', function (done) {
this.timeout(10000);
const term = new WindowsTerminal('cmd.exe', '', { useConpty, useConptyDll });
term.write('exit\r');
term.on('exit', () => {
done();
});
});
});
});
});
}

203
node_modules/node-pty/src/windowsTerminal.ts generated vendored Normal file
View File

@@ -0,0 +1,203 @@
/**
* Copyright (c) 2012-2015, Christopher Jeffrey, Peter Sunde (MIT License)
* Copyright (c) 2016, Daniel Imms (MIT License).
* Copyright (c) 2018, Microsoft Corporation (MIT License).
*/
import { Socket } from 'net';
import { Terminal, DEFAULT_COLS, DEFAULT_ROWS } from './terminal';
import { WindowsPtyAgent } from './windowsPtyAgent';
import { IPtyOpenOptions, IWindowsPtyForkOptions } from './interfaces';
import { ArgvOrCommandLine } from './types';
import { assign } from './utils';
const DEFAULT_FILE = 'cmd.exe';
const DEFAULT_NAME = 'Windows Shell';
export class WindowsTerminal extends Terminal {
private _isReady: boolean;
private _deferreds: { run: () => void }[];
private _agent: WindowsPtyAgent;
constructor(file?: string, args?: ArgvOrCommandLine, opt?: IWindowsPtyForkOptions) {
super(opt);
this._checkType('args', args, 'string', true);
// Initialize arguments
args = args || [];
file = file || DEFAULT_FILE;
opt = opt || {};
opt.env = opt.env || process.env;
if (opt.encoding) {
console.warn('Setting encoding on Windows is not supported');
}
const env = assign({}, opt.env);
this._cols = opt.cols || DEFAULT_COLS;
this._rows = opt.rows || DEFAULT_ROWS;
const cwd = opt.cwd || process.cwd();
const name = opt.name || env.TERM || DEFAULT_NAME;
const parsedEnv = this._parseEnv(env);
// If the terminal is ready
this._isReady = false;
// Functions that need to run after `ready` event is emitted.
this._deferreds = [];
// Create new termal.
this._agent = new WindowsPtyAgent(file, args, parsedEnv, cwd, this._cols, this._rows, false, opt.useConpty, opt.useConptyDll, opt.conptyInheritCursor);
this._socket = this._agent.outSocket;
// Not available until `ready` event emitted.
this._pid = this._agent.innerPid;
this._fd = this._agent.fd;
this._pty = this._agent.pty;
// The forked windows terminal is not available until `ready` event is
// emitted.
this._socket.on('ready_datapipe', () => {
// Run deferreds and set ready state once the first data event is received.
this._socket.once('data', () => {
// Wait until the first data event is fired then we can run deferreds.
if (!this._isReady) {
// Terminal is now ready and we can avoid having to defer method
// calls.
this._isReady = true;
// Execute all deferred methods
this._deferreds.forEach(fn => {
// NB! In order to ensure that `this` has all its references
// updated any variable that need to be available in `this` before
// the deferred is run has to be declared above this forEach
// statement.
fn.run();
});
// Reset
this._deferreds = [];
}
});
// Shutdown if `error` event is emitted.
this._socket.on('error', err => {
// Close terminal session.
this._close();
// EIO, happens when someone closes our child process: the only process
// in the terminal.
// node < 0.6.14: errno 5
// node >= 0.6.14: read EIO
if ((<any>err).code) {
if (~(<any>err).code.indexOf('errno 5') || ~(<any>err).code.indexOf('EIO')) return;
}
// Throw anything else.
if (this.listeners('error').length < 2) {
throw err;
}
});
// Cleanup after the socket is closed.
this._socket.on('close', () => {
this.emit('exit', this._agent.exitCode);
this._close();
});
});
this._file = file;
this._name = name;
this._readable = true;
this._writable = true;
this._forwardEvents();
}
protected _write(data: string | Buffer): void {
this._defer(this._doWrite, data);
}
private _doWrite(data: string | Buffer): void {
this._agent.inSocket.write(data);
}
/**
* openpty
*/
public static open(options?: IPtyOpenOptions): void {
throw new Error('open() not supported on windows, use Fork() instead.');
}
/**
* TTY
*/
public resize(cols: number, rows: number): void {
if (cols <= 0 || rows <= 0 || isNaN(cols) || isNaN(rows) || cols === Infinity || rows === Infinity) {
throw new Error('resizing must be done using positive cols and rows');
}
this._deferNoArgs(() => {
this._agent.resize(cols, rows);
this._cols = cols;
this._rows = rows;
});
}
public clear(): void {
this._deferNoArgs(() => {
this._agent.clear();
});
}
public destroy(): void {
this._deferNoArgs(() => {
this.kill();
});
}
public kill(signal?: string): void {
this._deferNoArgs(() => {
if (signal) {
throw new Error('Signals not supported on windows.');
}
this._close();
this._agent.kill();
});
}
private _deferNoArgs<A>(deferredFn: () => void): void {
// If the terminal is ready, execute.
if (this._isReady) {
deferredFn.call(this);
return;
}
// Queue until terminal is ready.
this._deferreds.push({
run: () => deferredFn.call(this)
});
}
private _defer<A>(deferredFn: (arg: A) => void, arg: A): void {
// If the terminal is ready, execute.
if (this._isReady) {
deferredFn.call(this, arg);
return;
}
// Queue until terminal is ready.
this._deferreds.push({
run: () => deferredFn.call(this, arg)
});
}
public get process(): string { return this._name; }
public get master(): Socket { throw new Error('master is not supported on Windows'); }
public get slave(): Socket { throw new Error('slave is not supported on Windows'); }
}

22
node_modules/node-pty/src/worker/conoutSocketWorker.ts generated vendored Normal file
View File

@@ -0,0 +1,22 @@
/**
* Copyright (c) 2020, Microsoft Corporation (MIT License).
*/
import { parentPort, workerData } from 'worker_threads';
import { Socket, createServer } from 'net';
import { ConoutWorkerMessage, IWorkerData, getWorkerPipeName } from '../shared/conout';
const { conoutPipeName } = (workerData as IWorkerData);
const conoutSocket = new Socket();
conoutSocket.setEncoding('utf8');
conoutSocket.connect(conoutPipeName, () => {
const server = createServer(workerSocket => {
conoutSocket.pipe(workerSocket);
});
server.listen(getWorkerPipeName(conoutPipeName));
if (!parentPort) {
throw new Error('worker_threads parentPort is null');
}
parentPort.postMessage(ConoutWorkerMessage.READY);
});