///
import { readFileSync, readdirSync } from 'node:fs';
import { ptr } from 'bun:ffi';
import {
ECHILD,
ESRCH,
WNOHANG,
errno,
native,
pidfdAlive,
pidfdOpen,
pidfdSendSignal,
} from './host-supervisor-native';
type StableIdentity = {
pid: number;
startTime: string;
parentPid: number;
parentStartTime: string;
pidfd: number;
};
const identities = new Map();
let maxTrackedIdentities = 4_096;
let semanticRootPid: number | undefined;
let supervisorStartTime: string | undefined;
interface ProcIdentity {
readonly state: string;
readonly parentPid: number;
readonly startTime: string;
}
export function processIdentity(pid: number): ProcIdentity | undefined {
try {
const source = readFileSync(`/proc/${pid}/stat`, 'utf8');
const close = source.lastIndexOf(')');
if (close < 0) return undefined;
const fields = source
.slice(close + 2)
.trim()
.split(/\s+/);
const state = fields[0];
const parentPid = Number(fields[1]);
const startTime = fields[19];
if (
state === undefined ||
!Number.isSafeInteger(parentPid) ||
parentPid < 0 ||
startTime === undefined
)
return undefined;
return { state, parentPid, startTime };
} catch {
return undefined;
}
}
export function processStartTime(pid: number): string | undefined {
return processIdentity(pid)?.startTime;
}
function childPids(pid: number): number[] {
const children = new Set();
let tasks: string[];
try {
tasks = readdirSync(`/proc/${pid}/task`);
} catch {
return [];
}
for (const task of tasks) {
try {
for (const token of readFileSync(`/proc/${pid}/task/${task}/children`, 'utf8')
.trim()
.split(/\s+/)) {
const childPid = Number(token);
if (Number.isSafeInteger(childPid) && childPid > 1) children.add(childPid);
}
} catch {
// A thread may exit between listing and reading; kernel child ownership remains authoritative.
}
}
return [...children];
}
function openStableIdentity(
pid: number,
owner: {
readonly pid: number;
readonly startTime: string;
readonly pidfd?: number;
}
): StableIdentity | undefined {
if (owner.pidfd !== undefined && !pidfdAlive(owner.pidfd)) return undefined;
const ownerBefore = processIdentity(owner.pid);
const before = processIdentity(pid);
if (
ownerBefore?.startTime !== owner.startTime ||
before === undefined ||
before.state === 'Z' ||
before.parentPid !== owner.pid
)
return undefined;
const pidfd = pidfdOpen(pid);
if (pidfd < 0) {
if (errno() === ESRCH && processIdentity(pid) === undefined) return undefined;
throw new Error('pidfd_open unavailable for an owned process');
}
const ownerAfter = processIdentity(owner.pid);
if (owner.pidfd !== undefined && !pidfdAlive(owner.pidfd)) {
native.close(pidfd);
return undefined;
}
const after = processIdentity(pid);
if (
ownerAfter?.startTime !== owner.startTime ||
after?.startTime !== before.startTime ||
after.state === 'Z' ||
after.parentPid !== owner.pid
) {
native.close(pidfd);
return undefined;
}
return {
pid,
startTime: before.startTime,
parentPid: owner.pid,
parentStartTime: owner.startTime,
pidfd,
};
}
function retireExitedIdentities(): void {
const status = new Int32Array(1);
for (const identity of identities.values()) {
if (identity.pid !== semanticRootPid) {
const reaped = native.waitpid(identity.pid, ptr(status), WNOHANG);
if (reaped < 0 && errno() !== ECHILD) {
throw new Error('waitpid failed while retiring an owned identity');
}
}
const probe = pidfdSendSignal(identity.pidfd, 0);
if (probe < 0 && errno() === ESRCH) {
native.close(identity.pidfd);
identities.delete(identity.pid);
} else if (probe < 0) {
throw new Error('pidfd identity liveness probe failed');
}
}
}
export function discoverOwnedProcesses(): void {
retireExitedIdentities();
if (supervisorStartTime === undefined) throw new Error('supervisor identity unavailable');
const queue: Array<{ pid: number; startTime: string; pidfd?: number }> = [
{ pid: process.pid, startTime: supervisorStartTime },
];
for (const identity of identities.values()) {
queue.push({
pid: identity.pid,
startTime: identity.startTime,
pidfd: identity.pidfd,
});
}
const visited = new Set();
while (queue.length > 0) {
const owner = queue.shift();
if (owner === undefined || visited.has(owner.pid)) continue;
visited.add(owner.pid);
if (processStartTime(owner.pid) !== owner.startTime) continue;
if (owner.pidfd !== undefined && !pidfdAlive(owner.pidfd)) continue;
for (const childPid of childPids(owner.pid)) {
let identity = identities.get(childPid);
if (identity !== undefined && processStartTime(childPid) !== identity.startTime) {
native.close(identity.pidfd);
identities.delete(childPid);
identity = undefined;
}
if (identity === undefined) {
if (identities.size >= maxTrackedIdentities) {
throw new Error('owned process identity capacity exceeded');
}
identity = openStableIdentity(childPid, owner);
if (identity !== undefined) identities.set(childPid, identity);
}
if (identity !== undefined) {
queue.push({
pid: identity.pid,
startTime: identity.startTime,
pidfd: identity.pidfd,
});
}
}
}
retireExitedIdentities();
}
export function signalTracked(signal: number): void {
for (const identity of identities.values()) {
const result = pidfdSendSignal(identity.pidfd, signal);
if (result < 0 && errno() === ESRCH) {
native.close(identity.pidfd);
identities.delete(identity.pid);
} else if (result < 0) {
throw new Error('pidfd_send_signal failed for an owned process');
}
}
}
export function signalOwned(signal: number): void {
discoverOwnedProcesses();
signalTracked(signal);
}
export function reapExitedOwned(rootPid: number): void {
const status = new Int32Array(1);
for (const identity of identities.values()) {
if (identity.pid === rootPid) continue;
const result = native.waitpid(identity.pid, ptr(status), WNOHANG);
if (result < 0 && errno() !== ECHILD) {
throw new Error('waitpid failed for an owned process');
}
}
}
export function ownershipEmpty(): boolean {
const status = new Int32Array(1);
for (;;) {
const result = native.waitpid(-1, ptr(status), WNOHANG);
if (result > 0) continue;
if (result === 0) return false;
if (errno() === ECHILD) return true;
throw new Error('kernel child ownership could not be determined');
}
}
export function initializeProcessTracker(startTime: string, identityCap: number): void {
supervisorStartTime = startTime;
maxTrackedIdentities = identityCap;
}
export function trackSemanticRoot(pid: number): boolean {
semanticRootPid = pid;
if (supervisorStartTime === undefined) throw new Error('supervisor identity unavailable');
const rootIdentity = openStableIdentity(pid, {
pid: process.pid,
startTime: supervisorStartTime,
});
if (rootIdentity === undefined) return false;
identities.set(rootIdentity.pid, rootIdentity);
return true;
}
export function closeTrackedIdentities(): void {
for (const identity of identities.values()) native.close(identity.pidfd);
identities.clear();
}