/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. * * Copied from https://github.com/microsoft/vscode-node-debug/blob/master/src/node/extension/processTree.ts *--------------------------------------------------------------------------------------------*/ /* tslint:disable */ 'use strict'; import { spawn, ChildProcessWithoutNullStreams } from 'child_process'; import { join } from 'path'; export class ProcessTreeNode { children?: ProcessTreeNode[]; constructor(public pid: number, public ppid: number, public command: string, public args: string) { } } export async function getProcessTree(rootPid: number) : Promise { const map = new Map(); map.set(0, new ProcessTreeNode(0, 0, '???', '')); try { await getProcesses((pid: number, ppid: number, command: string, args: string) => { if (pid !== ppid) { map.set(pid, new ProcessTreeNode(pid, ppid, command, args)); } }); } catch (err) { return undefined; } const values = map.values(); for (const p of values) { const parent = map.get(p.ppid); if (parent && parent !== p) { if (!parent.children) { parent.children = []; } parent.children.push(p); } } if (!isNaN(rootPid) && rootPid > 0) { return map.get(rootPid); } return map.get(0); } export function getProcesses(one: (pid: number, ppid: number, command: string, args: string, date?: number) => void) : Promise { // returns a function that aggregates chunks of data until one or more complete lines are received and passes them to a callback. function lines(callback: (a: string) => void) { let unfinished = ''; // unfinished last line of chunk return (data: string | Buffer) => { const lines = data.toString().split(/\r?\n/); const finishedLines = lines.slice(0, lines.length - 1); finishedLines[0] = unfinished + finishedLines[0]; // complete previous unfinished line unfinished = lines[lines.length - 1]; // remember unfinished last line of this chunk for next round for (const s of finishedLines) { callback(s); } }; } return new Promise((resolve, reject) => { let proc: ChildProcessWithoutNullStreams; if (process.platform === 'win32') { // attributes columns are in alphabetic order! const CMD_PAT = /^(.*)\s+([0-9]+)\.[0-9]+[+-][0-9]+\s+([0-9]+)\s+([0-9]+)$/; const wmic = join(process.env['WINDIR'] || 'C:\\Windows', 'System32', 'wbem', 'WMIC.exe'); proc = spawn(wmic, [ 'process', 'get', 'CommandLine,CreationDate,ParentProcessId,ProcessId' ]); proc.stdout.setEncoding('utf8'); proc.stdout.on('data', lines(line => { let matches = CMD_PAT.exec(line.trim()); if (matches && matches.length === 5) { const pid = Number(matches[4]); const ppid = Number(matches[3]); const date = Number(matches[2]); let args = matches[1].trim(); if (!isNaN(pid) && !isNaN(ppid) && args) { let command = args; if (args[0] === '"') { const end = args.indexOf('"', 1); if (end > 0) { command = args.substr(1, end-1); args = args.substr(end + 2); } } else { const end = args.indexOf(' '); if (end > 0) { command = args.substr(0, end); args = args.substr(end + 1); } else { args = ''; } } one(pid, ppid, command, args, date); } } })); } else if (process.platform === 'darwin') { // OS X proc = spawn('/bin/ps', [ '-x', '-o', `pid,ppid,comm=${'a'.repeat(256)},command` ]); proc.stdout.setEncoding('utf8'); proc.stdout.on('data', lines(line => { const pid = Number(line.substr(0, 5)); const ppid = Number(line.substr(6, 5)); const command = line.substr(12, 256).trim(); const args = line.substr(269 + command.length); if (!isNaN(pid) && !isNaN(ppid)) { one(pid, ppid, command, args); } })); } else { // linux proc = spawn('/bin/ps', [ '-ax', '-o', 'pid:6,ppid:6,comm:20,command' ]); // we specify the column width explicitly proc.stdout.setEncoding('utf8'); proc.stdout.on('data', lines(line => { // the following substr arguments must match the column width specified for the "ps" command above // regular substr is deprecated const pid = Number(substr(line, 0, 6)); const ppid = Number(substr(line, 7, 6)); const shortName = substr(line, 14, 20).trim() const fullCommand = substr(line, 35) let command = shortName; let args = fullCommand; const pos = fullCommand.indexOf(shortName); if (pos >= 0) { // binaries with spaces in path may not work // possible solution to read directly from /proc const commandEndPositionMaybe = fullCommand.indexOf(" ", pos + shortName.length); const commandEndPosition = commandEndPositionMaybe < 0 ? fullCommand.length : commandEndPositionMaybe; command = fullCommand.substring(0, commandEndPosition) args = fullCommand.substring(commandEndPosition).trimStart() } if (!isNaN(pid) && !isNaN(ppid)) { one(pid, ppid, command, args); } })); } proc.on('error', err => { reject(err); }); proc.stderr.setEncoding('utf8'); proc.stderr.on('data', data => { const e = data.toString(); if (e.indexOf('screen size is bogus') >= 0) { // ignore this error silently; see https://github.com/microsoft/vscode/issues/75932 } else { reject(new Error(data.toString())); } }); proc.on('close', (code, signal) => { if (code === 0) { resolve(); } else if (code !== null && code > 0) { reject(new Error(`process terminated with exit code: ${code}`)); } if (signal) { reject(new Error(`process terminated with signal: ${signal}`)); } }); proc.on('exit', (code, signal) => { if (typeof code === 'number') { if (code === 0) { //resolve(); } else if (code > 0) { reject(new Error(`process terminated with exit code: ${code}`)); } } if (signal) { reject(new Error(`process terminated with signal: ${signal}`)); } }); }); } function substr(str: string, startIndex: number, length?: number) { return str.slice(startIndex, length != undefined ? startIndex + length : str.length) }