const { spawn } = require('child_process'); const whitespace = /\s+/; const validFields = new Set([ 'group', 'ppid', 'user', 'args', 'comm', 'rgroup', 'nice', 'pid', 'pgid', 'etime', 'ruser', 'time', 'tty', 'vsz' ]); const lookup = async ({ pid, ppid, user, group, command, all, fields = ['pid', 'ppid', 'comm'] }) => { if (! fields.every((field) => validFields.has(field))) { throw new Error('ps - invalid field provided'); } if (ppid && fields.indexOf('ppid') < 0) { fields.push('ppid'); } const fieldOptions = fields.reduce(reduceFieldsToOptions, [ ]); if (pid || pid === 0) { const output = await ps([ '-p', processPids(pid), ...fieldOptions ]); return parseGrid(output); } if (ppid || ppid === 0) { const pids = processPids(ppid); const ppidSet = new Set(String(pids).split(',')); const output = await ps([ '-p', pids, ...fieldOptions ]); const processes = parseGrid(output); return processes.filter((proc) => { return ppidSet.has(proc.ppid); }); } if (all) { const output = await ps([ '-e', ...fieldOptions ]); return parseGrid(output); } if (user) { const output = await ps([ '-u', user, ...fieldOptions ]); return parseGrid(output); } if (group) { const output = await ps([ '-g', group, ...fieldOptions ]); return parseGrid(output); } if (command) { const output = await ps([ '-C', command, ...fieldOptions ]); return parseGrid(output); } throw new Error('ps - must provide a search query; one of "pid", "ppid", "all", "user", "group", "command"'); }; // Export module.exports = lookup; lookup.lookup = lookup; class Process { constructor(fields) { Object.assign(this, fields); Object.freeze(this); } } // Turns ['comm', 'pid'] into ['-o', 'comm', '-o', 'pid'] const reduceFieldsToOptions = (options, field) => { options.push('-o', field); return options; }; const ps = (args, callback) => { return new Promise((resolve, reject) => { const child = spawn('ps', args); let stdout = ''; let stderr = ''; child.on('error', (error) => { reject(error); }); child.stdout.on('data', (data) => stdout += data); child.stderr.on('data', (data) => stderr += data); child.on('close', (code) => { stderr = stderr.trim(); if (stderr) { return reject(new Error(stderr)); } resolve(stdout.trim()); }); }); }; const isValidPid = (pid) => { const int = parseInt(pid, 10); return ! isNaN(int) && String(int) === String(pid); }; const processPids = (pid) => { if (Array.isArray(pid)) { if (! pid.every(isValidPid)) { throw new Error('ps - pid must be a valid integer value'); } return pid.join(','); } if (! isValidPid(pid)) { throw new Error('ps - pid must be a valid integer value'); } return pid; } // Parses the resulting output from ps into `Process` objects const parseGrid = (output) => { if (! output) { return [ ]; } const lines = output.split('\n'); const header = lines.shift() .trim() .split(whitespace) .map((col) => col.toLowerCase()); return lines.map((line) => { const proc = { }; line.trim().split(whitespace).forEach((item, index) => { if (item) { proc[header[index]] = item; } }); return new Process(proc); }); };