chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
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<typeof spawn> | 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<typeof parseFrame>,
|
||||
code: number | null,
|
||||
signal: NodeJS.Signals | null,
|
||||
error?: Error,
|
||||
): Promise<void> {
|
||||
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<void> {
|
||||
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<string, unknown>;
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user