import { spawn } from "node:child_process"; import { closeSync, createReadStream, writeSync } from "node:fs"; import { cleanupOwnedProviderReport } from "./provider-raw-cleanup.ts"; const MAX_FRAME_BYTES = 16_777_216; const reportIdentity = parseReportIdentity(process.argv.slice(2)); let pending = Buffer.alloc(0); let expectedBytes: number | undefined; let provider: ReturnType | undefined; let providerClosed = false; let livenessLost = false; const liveness = createReadStream("", { fd: 0, autoClose: false }); liveness.on("data", (chunk: Buffer | string) => { if (provider) { terminateForProtocolFailure("provider scope received trailing protocol bytes"); return; } pending = Buffer.concat([pending, Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)]); if (expectedBytes === undefined && pending.byteLength >= 4) { expectedBytes = pending.readUInt32BE(0); if (expectedBytes <= 0 || expectedBytes > MAX_FRAME_BYTES) { terminateForProtocolFailure("provider scope frame length is invalid"); return; } } if (expectedBytes !== undefined && pending.byteLength === expectedBytes + 4) { launchProvider(pending.subarray(4)); pending = Buffer.alloc(0); } else if (expectedBytes !== undefined && pending.byteLength > expectedBytes + 4) { terminateForProtocolFailure("provider scope frame has trailing bytes"); } }); liveness.once("end", () => terminateForParentLoss()); liveness.once("error", () => terminateForParentLoss()); function launchProvider(payload: Buffer): void { const frame = parseFrame(payload); if ( frame.reportPath !== reportIdentity.reportPath || frame.reportDev !== reportIdentity.reportDev || frame.reportIno !== reportIdentity.reportIno ) { throw new TypeError("provider scope frame identity does not match its launch identity"); } const bwrapInput = Buffer.from(frame.bwrapInputBase64, "base64"); provider = spawn("/usr/bin/bwrap", ["--args", "0"], { detached: true, stdio: ["pipe", "inherit", "inherit"], }); provider.stdin?.end(bwrapInput); provider.once("error", (error) => finishProvider(frame, null, null, error)); provider.once("close", (code, signal) => finishProvider(frame, code, signal)); } async function finishProvider( frame: ReturnType, code: number | null, signal: NodeJS.Signals | null, error?: Error, ): Promise { if (providerClosed) return; providerClosed = true; if (livenessLost) await cleanupOwnedProviderReport(frame); closeLivenessInput(); if (error) { writeSync(2, `${error.message}\n`); process.exit(1); } if (signal) process.exit(128 + signalNumber(signal)); process.exit(code ?? 1); } function terminateForParentLoss(): void { if (livenessLost) return; livenessLost = true; if (!provider || providerClosed) { void cleanupAfterParentLossAndExit(); return; } try { process.kill(-provider.pid!, "SIGKILL"); } catch (error) { if (!hasErrorCode(error, "ESRCH")) throw error; } } async function cleanupAfterParentLossAndExit(): Promise { try { await cleanupOwnedProviderReport(reportIdentity); } catch (error) { writeSync(2, `${error instanceof Error ? error.message : String(error)}\n`); } closeLivenessInput(); process.exit(125); } function terminateForProtocolFailure(message: string): void { writeSync(2, `${message}\n`); terminateForParentLoss(); } function closeLivenessInput(): void { liveness.removeAllListeners(); liveness.destroy(); try { closeSync(0); } catch (error) { if (!hasErrorCode(error, "EBADF")) throw error; } } function parseFrame(payload: Buffer): Readonly<{ bwrapInputBase64: string; reportPath: string; reportDev: number; reportIno: number; }> { const value = JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(payload)) as Record; if ( typeof value.bwrapInputBase64 !== "string" || typeof value.reportPath !== "string" || !value.reportPath.startsWith("/") || !Number.isSafeInteger(value.reportDev) || Number(value.reportDev) <= 0 || !Number.isSafeInteger(value.reportIno) || Number(value.reportIno) <= 0 ) { throw new TypeError("provider scope frame payload is invalid"); } return { bwrapInputBase64: value.bwrapInputBase64, reportPath: value.reportPath, reportDev: Number(value.reportDev), reportIno: Number(value.reportIno), }; } function parseReportIdentity(arguments_: readonly string[]): Readonly<{ cpuSeconds: number; reportPath: string; reportDev: number; reportIno: number; }> { const [cpuValue, reportPath, devValue, inoValue, ...trailing] = arguments_; const cpuSeconds = Number(cpuValue); const reportDev = Number(devValue); const reportIno = Number(inoValue); if ( trailing.length > 0 || !Number.isSafeInteger(cpuSeconds) || cpuSeconds <= 0 || typeof reportPath !== "string" || !reportPath.startsWith("/") || reportPath.includes("\0") || !Number.isSafeInteger(reportDev) || reportDev <= 0 || !Number.isSafeInteger(reportIno) || reportIno <= 0 ) { throw new TypeError("provider scope launch identity is invalid"); } return { cpuSeconds, reportPath, reportDev, reportIno }; } function signalNumber(signal: NodeJS.Signals): number { return signal === "SIGKILL" ? 9 : signal === "SIGXCPU" ? 24 : 1; } function hasErrorCode(error: unknown, code: string): boolean { return Boolean(error && typeof error === "object" && "code" in error && error.code === code); }