chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 deletions

View File

@@ -41,7 +41,15 @@ export interface RunCommandPipeArgs {
args: any[];
}
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs;
export interface ExtensionManagementPipeArgs {
type: 'extensionManagement';
list?: { showVersions?: boolean, category?: string; };
install?: string[];
uninstall?: string[];
force?: boolean;
}
export type PipeCommand = OpenCommandPipeArgs | StatusPipeArgs | RunCommandPipeArgs | OpenExternalCommandPipeArgs | ExtensionManagementPipeArgs;
export interface ICommandsExecuter {
executeCommand<T>(id: string, ...args: any[]): Promise<T>;
@@ -102,6 +110,10 @@ export class CLIServerBase {
this.runCommand(data, res)
.catch(this.logService.error);
break;
case 'extensionManagement':
this.manageExtensions(data, res)
.catch(this.logService.error);
break;
default:
res.writeHead(404);
res.write(`Unknown message type: ${data.type}`, err => {
@@ -150,14 +162,34 @@ export class CLIServerBase {
res.end();
}
private openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) {
private async openExternal(data: OpenExternalCommandPipeArgs, res: http.ServerResponse) {
for (const uri of data.uris) {
this._commands.executeCommand('_workbench.openExternal', URI.parse(uri), { allowTunneling: true });
await this._commands.executeCommand('_remoteCLI.openExternal', URI.parse(uri), { allowTunneling: true });
}
res.writeHead(200);
res.end();
}
private async manageExtensions(data: ExtensionManagementPipeArgs, res: http.ServerResponse) {
console.log('server: manageExtensions');
try {
const toExtOrVSIX = (inputs: string[] | undefined) => inputs?.map(input => /\.vsix$/i.test(input) ? URI.parse(input) : input);
const commandArgs = {
list: data.list,
install: toExtOrVSIX(data.install),
uninstall: toExtOrVSIX(data.uninstall),
force: data.force
};
const output = await this._commands.executeCommand('_remoteCLI.manageExtensions', commandArgs, { allowTunneling: true });
res.writeHead(200);
res.write(output);
} catch (e) {
res.writeHead(500);
res.write(String(e));
}
res.end();
}
private async getStatus(data: StatusPipeArgs, res: http.ServerResponse) {
try {
const status = await this._commands.executeCommand('_issues.getSystemStatus');

View File

@@ -104,7 +104,7 @@ export class ExtHostDebugService extends ExtHostDebugServiceBase {
cwdForPrepareCommand = args.cwd;
}
terminal.show();
terminal.show(true);
const shellProcessId = await terminal.processId;

View File

@@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as performance from 'vs/base/common/performance';
import { createApiFactoryAndRegisterActors } from 'vs/workbench/api/common/extHost.api.impl';
import { RequireInterceptor } from 'vs/workbench/api/common/extHostRequireInterceptor';
import { MainContext } from 'vs/workbench/api/common/extHost.protocol';
@@ -13,7 +14,7 @@ import { ExtHostDownloadService } from 'vs/workbench/api/node/extHostDownloadSer
import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtensionRuntime } from 'vs/workbench/api/common/extHostTypes';
class NodeModuleRequireInterceptor extends RequireInterceptor {
@@ -62,10 +63,12 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
// Module loading tricks
const interceptor = this._instaService.createInstance(NodeModuleRequireInterceptor, extensionApiFactory, this._registry);
await interceptor.install();
performance.mark('code/extHost/didInitAPI');
// Do this when extension service exists, but extensions are not being activated yet.
const configProvider = await this._extHostConfiguration.getConfigProvider();
await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._logService, this._mainThreadTelemetryProxy, this._initData);
performance.mark('code/extHost/didInitProxyResolver');
// Use IPC messages to forward console-calls, note that the console is
// already patched to use`process.send()`
@@ -84,7 +87,7 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
return extensionDescription.main;
}
protected _loadCommonJSModule<T>(module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
protected _loadCommonJSModule<T>(extensionId: ExtensionIdentifier | null, module: URI, activationTimesBuilder: ExtensionActivationTimesBuilder): Promise<T> {
if (module.scheme !== Schemas.file) {
throw new Error(`Cannot load URI: '${module}', must be of file-scheme`);
}
@@ -93,10 +96,16 @@ export class ExtHostExtensionService extends AbstractExtHostExtensionService {
this._logService.info(`ExtensionService#loadCommonJSModule ${module.toString(true)}`);
this._logService.flush();
try {
if (extensionId) {
performance.mark(`code/extHost/willLoadExtensionCode/${extensionId.value}`);
}
r = require.__$__nodeRequire<T>(module.fsPath);
} catch (e) {
return Promise.reject(e);
} finally {
if (extensionId) {
performance.mark(`code/extHost/didLoadExtensionCode/${extensionId.value}`);
}
activationTimesBuilder.codeLoadingStop();
}
return Promise.resolve(r);

View File

@@ -50,11 +50,12 @@ export class ExtHostTask extends ExtHostTaskBase {
}
public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise<vscode.TaskExecution> {
if (!task.execution) {
const tTask = (task as types.Task);
if (!task.execution && (tTask._id === undefined)) {
throw new Error('Tasks to execute must include an execution');
}
const tTask = (task as types.Task);
// We have a preserved ID. So the task didn't change.
if (tTask._id !== undefined) {
// Always get the task execution first to prevent timing issues when retrieving it later
@@ -121,7 +122,7 @@ export class ExtHostTask extends ExtHostTaskBase {
private async getVariableResolver(workspaceFolders: vscode.WorkspaceFolder[]): Promise<ExtHostVariableResolverService> {
if (this._variableResolver === undefined) {
const configProvider = await this._configurationService.getConfigProvider();
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment);
this._variableResolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider, process.env as IProcessEnvironment, this.workspaceService);
}
return this._variableResolver;
}

View File

@@ -17,13 +17,15 @@ import { ExtHostWorkspace, IExtHostWorkspace } from 'vs/workbench/api/common/ext
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ExtHostVariableResolverService } from 'vs/workbench/api/common/extHostDebugService';
import { ExtHostDocumentsAndEditors, IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { getSystemShell, detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
import { detectAvailableShells } from 'vs/workbench/contrib/terminal/node/terminal';
import { getMainProcessParentEnv } from 'vs/workbench/contrib/terminal/node/terminalEnvironment';
import { BaseExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/common/extHostTerminalService';
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
import { MergedEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariableCollection';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { withNullAsUndefined } from 'vs/base/common/types';
import { getSystemShell, getSystemShellSync } from 'vs/base/node/shell';
import { generateUuid } from 'vs/base/common/uuid';
export class ExtHostTerminalService extends BaseExtHostTerminalService {
@@ -32,6 +34,7 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
// TODO: Pull this from main side
private _isWorkspaceShellAllowed: boolean = false;
private _defaultShell: string | undefined;
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@@ -42,20 +45,26 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
@IExtHostInitDataService private _extHostInitDataService: IExtHostInitDataService
) {
super(true, extHostRpc);
// Getting the SystemShell is an async operation, however, the ExtHost terminal service is mostly synchronous
// and the API `vscode.env.shell` is also synchronous. The default shell _should_ be set when extensions are
// starting up but if not, we run getSystemShellSync below which gets a sane default.
getSystemShell(platform.platform).then(s => this._defaultShell = s);
this._updateLastActiveWorkspace();
this._updateVariableResolver();
this._registerListeners();
}
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[] | string): vscode.Terminal {
const terminal = new ExtHostTerminal(this._proxy, { name, shellPath, shellArgs }, name);
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), { name, shellPath, shellArgs }, name);
this._terminals.push(terminal);
terminal.create(shellPath, shellArgs);
return terminal;
}
public createTerminalFromOptions(options: vscode.TerminalOptions, isFeatureTerminal?: boolean): vscode.Terminal {
const terminal = new ExtHostTerminal(this._proxy, options, options.name);
const terminal = new ExtHostTerminal(this._proxy, generateUuid(), options, options.name);
this._terminals.push(terminal);
terminal.create(
withNullAsUndefined(options.shellPath),
@@ -76,10 +85,11 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
.inspect<string | string[]>(key.substr(key.lastIndexOf('.') + 1));
return this._apiInspectConfigToPlain<string | string[]>(setting);
};
return terminalEnvironment.getDefaultShell(
fetchSetting,
this._isWorkspaceShellAllowed,
getSystemShell(platform.platform),
this._defaultShell ?? getSystemShellSync(platform.platform),
process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'),
process.env.windir,
terminalEnvironment.createVariableResolver(this._lastActiveWorkspace, this._variableResolver),
@@ -139,7 +149,8 @@ export class ExtHostTerminalService extends BaseExtHostTerminalService {
executable: shellLaunchConfigDto.executable,
args: shellLaunchConfigDto.args,
cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd),
env: shellLaunchConfigDto.env
env: shellLaunchConfigDto.env,
flowControl: shellLaunchConfigDto.flowControl
};
// Merge in shell and args from settings

View File

@@ -12,12 +12,16 @@ import { URI } from 'vs/base/common/uri';
import { exec } from 'child_process';
import * as resources from 'vs/base/common/resources';
import * as fs from 'fs';
import * as pfs from 'vs/base/node/pfs';
import { isLinux } from 'vs/base/common/platform';
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { asPromise } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { promisify } from 'util';
import { MovingAverage } from 'vs/base/common/numbers';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ILogService } from 'vs/platform/log/common/log';
class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
@@ -26,14 +30,106 @@ class ExtensionTunnel implements vscode.Tunnel {
constructor(
public readonly remoteAddress: { port: number, host: string },
public readonly localAddress: { port: number, host: string } | string,
private readonly _dispose: () => void) { }
private readonly _dispose: () => Promise<void>) { }
dispose(): void {
dispose(): Promise<void> {
this._onDispose.fire();
this._dispose();
return this._dispose();
}
}
export function getSockets(stdout: string): { pid: number, socket: number }[] {
const lines = stdout.trim().split('\n');
const mapped: { pid: number, socket: number }[] = [];
lines.forEach(line => {
const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!;
if (match && match.length >= 3) {
mapped.push({
pid: parseInt(match[1], 10),
socket: parseInt(match[2], 10)
});
}
});
return mapped;
}
export function loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] {
const table = ([] as Record<string, string>[]).concat(...stdouts.map(loadConnectionTable));
return [
...new Map(
table.filter(row => row.st === '0A')
.map(row => {
const address = row.local_address.split(':');
return {
socket: parseInt(row.inode, 10),
ip: parseIpAddress(address[0]),
port: parseInt(address[1], 16)
};
}).map(port => [port.ip + ':' + port.port, port])
).values()
];
}
export function parseIpAddress(hex: string): string {
let result = '';
if (hex.length === 8) {
for (let i = hex.length - 2; i >= 0; i -= 2) {
result += parseInt(hex.substr(i, 2), 16);
if (i !== 0) {
result += '.';
}
}
} else {
for (let i = hex.length - 4; i >= 0; i -= 4) {
result += parseInt(hex.substr(i, 4), 16).toString(16);
if (i !== 0) {
result += ':';
}
}
}
return result;
}
export function loadConnectionTable(stdout: string): Record<string, string>[] {
const lines = stdout.trim().split('\n');
const names = lines.shift()!.trim().split(/\s+/)
.filter(name => name !== 'rx_queue' && name !== 'tm->when');
const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => {
obj[names[i] || i] = value;
return obj;
}, {} as Record<string, string>));
return table;
}
function knownExcludeCmdline(command: string): boolean {
return !!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/)
|| (command.indexOf('out/vs/server/main.js') !== -1)
|| (command.indexOf('_productName=VSCode') !== -1);
}
export async function findPorts(tcp: string, tcp6: string, procSockets: string, processes: { pid: number, cwd: string, cmd: string }[]): Promise<CandidatePort[]> {
const connections: { socket: number, ip: string, port: number }[] = loadListeningPorts(tcp, tcp6);
const sockets = getSockets(procSockets);
const socketMap = sockets.reduce((m, socket) => {
m[socket.socket] = socket;
return m;
}, {} as Record<string, typeof sockets[0]>);
const processMap = processes.reduce((m, process) => {
m[process.pid] = process;
return m;
}, {} as Record<string, typeof processes[0]>);
const ports: CandidatePort[] = [];
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
const command = processMap[socketMap[socket].pid].cmd;
if (!knownExcludeCmdline(command)) {
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd, pid: socketMap[socket].pid });
}
});
return ports;
}
export class ExtHostTunnelService extends Disposable implements IExtHostTunnelService {
readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadTunnelServiceShape;
@@ -42,15 +138,17 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
private _extensionTunnels: Map<string, Map<number, { tunnel: vscode.Tunnel, disposeListener: IDisposable }>> = new Map();
private _onDidChangeTunnels: Emitter<void> = new Emitter<void>();
onDidChangeTunnels: vscode.Event<void> = this._onDidChangeTunnels.event;
private _candidateFindingEnabled: boolean = false;
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService private readonly logService: ILogService
) {
super();
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
if (initData.remote.isRemote && initData.remote.authority) {
this.registerCandidateFinder();
if (isLinux && initData.remote.isRemote && initData.remote.authority) {
this._proxy.$setCandidateFinder();
}
}
@@ -70,18 +168,30 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
return this._proxy.$getTunnels();
}
registerCandidateFinder(): void {
// Every two seconds, scan to see if the candidate ports have changed;
if (isLinux) {
let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined;
setInterval(async () => {
const newPorts = await this.findCandidatePorts();
if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) {
oldPorts = newPorts;
this._proxy.$onFoundNewCandidates(oldPorts.filter(async (candidate) => await this._showCandidatePort(candidate.host, candidate.port, candidate.detail)));
return;
}
}, 2000);
private calculateDelay(movingAverage: number) {
// Some local testing indicated that the moving average might be between 50-100 ms.
return Math.max(movingAverage * 20, 2000);
}
async $registerCandidateFinder(enable: boolean): Promise<void> {
if (enable && this._candidateFindingEnabled) {
// already enabled
return;
}
this._candidateFindingEnabled = enable;
// Regularly scan to see if the candidate ports have changed.
let movingAverage = new MovingAverage();
let oldPorts: { host: string, port: number, detail: string }[] | undefined = undefined;
while (this._candidateFindingEnabled) {
const startTime = new Date().getTime();
const newPorts = await this.findCandidatePorts();
const timeTaken = new Date().getTime() - startTime;
movingAverage.update(timeTaken);
if (!oldPorts || (JSON.stringify(oldPorts) !== JSON.stringify(newPorts))) {
oldPorts = newPorts;
await this._proxy.$onFoundNewCandidates(oldPorts);
}
await (new Promise<void>(resolve => setTimeout(() => resolve(), this.calculateDelay(movingAverage.value))));
}
}
@@ -89,15 +199,18 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
if (provider) {
if (provider.showCandidatePort) {
this._showCandidatePort = provider.showCandidatePort;
await this._proxy.$setCandidateFilter();
}
if (provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider();
await this._proxy.$setTunnelProvider(provider.tunnelFeatures ?? {
elevation: false,
public: false
});
}
} else {
this._forwardPortProvider = undefined;
}
await this._proxy.$tunnelServiceReady();
return toDisposable(() => {
this._forwardPortProvider = undefined;
});
@@ -110,7 +223,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
if (silent) {
hostMap.get(remote.port)!.disposeListener.dispose();
}
hostMap.get(remote.port)!.tunnel.dispose();
await hostMap.get(remote.port)!.tunnel.dispose();
hostMap.delete(remote.port);
}
}
@@ -120,31 +233,42 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
this._onDidChangeTunnels.fire();
}
$forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto> | undefined {
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
if (this._forwardPortProvider) {
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
if (providedPort !== undefined) {
return asPromise(() => providedPort).then(tunnel => {
try {
this.logService.trace('$forwardPort: Getting tunnel from provider.');
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
this.logService.trace('$forwardPort: Got tunnel promise from provider.');
if (providedPort !== undefined) {
const tunnel = await providedPort;
this.logService.trace('$forwardPort: Successfully awaited tunnel from provider.');
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
}
const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener });
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
});
return TunnelDto.fromApiTunnel(tunnel);
} else {
this.logService.trace('$forwardPort: Tunnel is undefined');
}
} catch (e) {
this.logService.trace('$forwardPort: tunnel provider error');
}
}
return undefined;
}
async $applyCandidateFilter(candidates: CandidatePort[]): Promise<CandidatePort[]> {
const filter = await Promise.all(candidates.map(candidate => this._showCandidatePort(candidate.host, candidate.port, candidate.detail)));
return candidates.filter((candidate, index) => filter[index]);
}
async findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]> {
const ports: { host: string, port: number, detail: string }[] = [];
async findCandidatePorts(): Promise<CandidatePort[]> {
let tcp: string = '';
let tcp6: string = '';
try {
tcp = fs.readFileSync('/proc/net/tcp', 'utf8');
tcp6 = fs.readFileSync('/proc/net/tcp6', 'utf8');
tcp = await pfs.readFile('/proc/net/tcp', 'utf8');
tcp6 = await pfs.readFile('/proc/net/tcp6', 'utf8');
} catch (e) {
// File reading error. No additional handling needed.
}
@@ -154,105 +278,24 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
});
}));
const procChildren = fs.readdirSync('/proc');
const processes: { pid: number, cwd: string, cmd: string }[] = [];
const procChildren = await pfs.readdir('/proc');
const processes: {
pid: number, cwd: string, cmd: string
}[] = [];
for (let childName of procChildren) {
try {
const pid: number = Number(childName);
const childUri = resources.joinPath(URI.file('/proc'), childName);
const childStat = fs.statSync(childUri.fsPath);
const childStat = await pfs.stat(childUri.fsPath);
if (childStat.isDirectory() && !isNaN(pid)) {
const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath);
const cmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
const cwd = await promisify(fs.readlink)(resources.joinPath(childUri, 'cwd').fsPath);
const cmd = await pfs.readFile(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
processes.push({ pid, cwd, cmd });
}
} catch (e) {
//
}
}
const connections: { socket: number, ip: string, port: number }[] = this.loadListeningPorts(tcp, tcp6);
const sockets = this.getSockets(procSockets);
const socketMap = sockets.reduce((m, socket) => {
m[socket.socket] = socket;
return m;
}, {} as Record<string, typeof sockets[0]>);
const processMap = processes.reduce((m, process) => {
m[process.pid] = process;
return m;
}, {} as Record<string, typeof processes[0]>);
connections.filter((connection => socketMap[connection.socket])).forEach(({ socket, ip, port }) => {
const command = processMap[socketMap[socket].pid].cmd;
if (!command.match(/.*\.vscode-server-[a-zA-Z]+\/bin.*/) && (command.indexOf('out/vs/server/main.js') === -1)) {
ports.push({ host: ip, port, detail: processMap[socketMap[socket].pid].cmd });
}
});
return ports;
}
private getSockets(stdout: string): { pid: number, socket: number }[] {
const lines = stdout.trim().split('\n');
const mapped: { pid: number, socket: number }[] = [];
lines.forEach(line => {
const match = /\/proc\/(\d+)\/fd\/\d+ -> socket:\[(\d+)\]/.exec(line)!;
if (match && match.length >= 3) {
mapped.push({
pid: parseInt(match[1], 10),
socket: parseInt(match[2], 10)
});
}
});
return mapped;
}
private loadListeningPorts(...stdouts: string[]): { socket: number, ip: string, port: number }[] {
const table = ([] as Record<string, string>[]).concat(...stdouts.map(this.loadConnectionTable));
return [
...new Map(
table.filter(row => row.st === '0A')
.map(row => {
const address = row.local_address.split(':');
return {
socket: parseInt(row.inode, 10),
ip: this.parseIpAddress(address[0]),
port: parseInt(address[1], 16)
};
}).map(port => [port.ip + ':' + port.port, port])
).values()
];
}
private parseIpAddress(hex: string): string {
let result = '';
if (hex.length === 8) {
for (let i = hex.length - 2; i >= 0; i -= 2) {
result += parseInt(hex.substr(i, 2), 16);
if (i !== 0) {
result += '.';
}
}
} else {
for (let i = hex.length - 4; i >= 0; i -= 4) {
result += parseInt(hex.substr(i, 4), 16).toString(16);
if (i !== 0) {
result += ':';
}
}
}
return result;
}
private loadConnectionTable(stdout: string): Record<string, string>[] {
const lines = stdout.trim().split('\n');
const names = lines.shift()!.trim().split(/\s+/)
.filter(name => name !== 'rx_queue' && name !== 'tm->when');
const table = lines.map(line => line.trim().split(/\s+/).reduce((obj, value, i) => {
obj[names[i] || i] = value;
return obj;
}, {} as Record<string, string>));
return table;
return findPorts(tcp, tcp6, procSockets, processes);
}
}