56 lines
1.6 KiB
TypeScript
56 lines
1.6 KiB
TypeScript
|
|
import * as fs from 'fs';
|
||
|
|
|
||
|
|
// Get all descendant PIDs of the given root PID by reading /proc/<pid>/stat.
|
||
|
|
// Returns an array of descendant PIDs (excludes the root itself).
|
||
|
|
export async function getChildPids(rootPid: number): Promise<number[]> {
|
||
|
|
const parentMap = new Map<number, number[]>(); // parent → children
|
||
|
|
|
||
|
|
let entries: string[];
|
||
|
|
try {
|
||
|
|
entries = fs.readdirSync('/proc');
|
||
|
|
} catch {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
for (const entry of entries) {
|
||
|
|
const pid = parseInt(entry, 10);
|
||
|
|
if (isNaN(pid)) continue;
|
||
|
|
|
||
|
|
try {
|
||
|
|
const stat = fs.readFileSync(`/proc/${pid}/stat`, 'utf8');
|
||
|
|
// Format: pid (comm) state ppid ...
|
||
|
|
// comm can contain spaces and parentheses, so find the last ')' first
|
||
|
|
const closeParenIdx = stat.lastIndexOf(')');
|
||
|
|
if (closeParenIdx === -1) continue;
|
||
|
|
const afterComm = stat.slice(closeParenIdx + 2); // skip ') '
|
||
|
|
const fields = afterComm.split(' ');
|
||
|
|
const ppid = parseInt(fields[1], 10); // field index 1 after state is ppid
|
||
|
|
|
||
|
|
if (!parentMap.has(ppid)) {
|
||
|
|
parentMap.set(ppid, []);
|
||
|
|
}
|
||
|
|
parentMap.get(ppid)!.push(pid);
|
||
|
|
} catch {
|
||
|
|
// Process may have exited between readdir and readFile
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// BFS from rootPid to collect all descendants
|
||
|
|
const result: number[] = [];
|
||
|
|
const queue: number[] = [rootPid];
|
||
|
|
|
||
|
|
while (queue.length > 0) {
|
||
|
|
const current = queue.shift()!;
|
||
|
|
const children = parentMap.get(current);
|
||
|
|
if (children) {
|
||
|
|
for (const child of children) {
|
||
|
|
result.push(child);
|
||
|
|
queue.push(child);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|