refactor: adapter 구현중..
This commit is contained in:
@@ -0,0 +1,641 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
closeSync,
|
||||
fstatSync,
|
||||
fsyncSync,
|
||||
lstatSync,
|
||||
readlinkSync,
|
||||
readSync,
|
||||
type Stats,
|
||||
writeSync,
|
||||
createReadStream,
|
||||
} from "node:fs";
|
||||
import { link, lstat, unlink } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
|
||||
import {
|
||||
decodeProviderGuardianCommit,
|
||||
decodeProviderGuardianGuard,
|
||||
decodeProviderGuardianPublish,
|
||||
encodeProviderGuardianPublished,
|
||||
encodeProviderGuardianReady,
|
||||
MAX_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES,
|
||||
MAX_PROVIDER_GUARDIAN_LEASE_MS,
|
||||
providerGuardianRawStagingLeaf,
|
||||
providerGuardianSealedTempLeaf,
|
||||
type ProviderGuardianGuard,
|
||||
type ProviderGuardianKind,
|
||||
} from "./provider-guardian-protocol.ts";
|
||||
import { cleanupOwnedProviderReport } from "./provider-raw-cleanup.ts";
|
||||
|
||||
type OwnedIdentity = Readonly<{ dev: number; ino: number }>;
|
||||
type BootstrapAuthority = Readonly<{
|
||||
kind: ProviderGuardianKind;
|
||||
noncePrefix: string;
|
||||
rawStagingLeaf: string;
|
||||
rawStagingPath: string;
|
||||
rawPath: string;
|
||||
rawIdentity: OwnedIdentity;
|
||||
sealedTempLeaf: string;
|
||||
sealedTempPath: string;
|
||||
sealedPath: string;
|
||||
sealedIdentity: OwnedIdentity;
|
||||
}>;
|
||||
type BoundPrivateAuthority = Readonly<{
|
||||
identity: OwnedIdentity;
|
||||
leaf: string;
|
||||
noncePrefix: string;
|
||||
path: string;
|
||||
stem: string;
|
||||
}>;
|
||||
type GuardianTransaction = Readonly<{ guard: ProviderGuardianGuard }>;
|
||||
|
||||
const RAW_DIRECTORY_FD = 3;
|
||||
const EVIDENCE_DIRECTORY_FD = 4;
|
||||
const RAW_STAGING_FD = 5;
|
||||
const SEALED_TEMP_FD = 6;
|
||||
const RAW_DIRECTORY_PATH = `/proc/self/fd/${RAW_DIRECTORY_FD}`;
|
||||
const EVIDENCE_DIRECTORY_PATH = `/proc/self/fd/${EVIDENCE_DIRECTORY_FD}`;
|
||||
const RAW_STAGING_FD_PATH = `/proc/self/fd/${RAW_STAGING_FD}`;
|
||||
const SEALED_TEMP_FD_PATH = `/proc/self/fd/${SEALED_TEMP_FD}`;
|
||||
const RAW_STAGING_PATTERN =
|
||||
/^\.(vulnerability-report|provenance-attestation)\.json\.guardian-([0-9a-f]{32})\.raw\.tmp$/u;
|
||||
const SEALED_TEMP_PATTERN =
|
||||
/^\.(vulnerability-report|provenance-attestation)\.json\.guardian-([0-9a-f]{32})\.tmp$/u;
|
||||
|
||||
let privateFdsClosed = false;
|
||||
const bootstrap = await initializeBootstrap();
|
||||
let pending = Buffer.alloc(0);
|
||||
let expectedBytes: number | undefined;
|
||||
let state: "starting" | "guarding" | "published" | "commitPending" = "starting";
|
||||
let transaction: GuardianTransaction | undefined;
|
||||
let terminal = false;
|
||||
let deadline: NodeJS.Timeout | undefined;
|
||||
let operations = Promise.resolve();
|
||||
const liveness = createReadStream("", { fd: 0, autoClose: false });
|
||||
|
||||
liveness.on("data", consumeChunk);
|
||||
liveness.once("end", () => {
|
||||
enqueue(async () => {
|
||||
if (state === "commitPending" && pending.byteLength === 0 && expectedBytes === undefined) {
|
||||
await succeedOnCommittedEof();
|
||||
return;
|
||||
}
|
||||
if (state === "starting" && pending.byteLength > 0) {
|
||||
await failClosed(126, "provider guardian frame is truncated");
|
||||
return;
|
||||
}
|
||||
await failClosed(125, "provider guardian liveness EOF");
|
||||
});
|
||||
});
|
||||
liveness.once("error", (error) => {
|
||||
enqueue(async () => failClosed(125, `provider guardian liveness error: ${error.message}`));
|
||||
});
|
||||
|
||||
async function initializeBootstrap(): Promise<BootstrapAuthority> {
|
||||
const rawDirectory = path.resolve(process.cwd(), "provider-evidence/untrusted");
|
||||
const evidenceDirectory = path.resolve(process.cwd(), "provider-evidence");
|
||||
const failures: Error[] = [];
|
||||
const rawDirectoryValid = captureInheritedDirectory(
|
||||
RAW_DIRECTORY_FD,
|
||||
rawDirectory,
|
||||
"raw",
|
||||
failures,
|
||||
);
|
||||
const evidenceDirectoryValid = captureInheritedDirectory(
|
||||
EVIDENCE_DIRECTORY_FD,
|
||||
evidenceDirectory,
|
||||
"evidence",
|
||||
failures,
|
||||
);
|
||||
|
||||
const rawDescriptor = capturePrivateDescriptor(RAW_STAGING_FD, "raw staging", failures);
|
||||
const sealedDescriptor = capturePrivateDescriptor(SEALED_TEMP_FD, "sealed temp", failures);
|
||||
const rawAuthority = rawDescriptor && rawDirectoryValid
|
||||
? capturePrivateAlias({
|
||||
descriptorMetadata: rawDescriptor,
|
||||
descriptorTarget: RAW_STAGING_FD_PATH,
|
||||
expectedDirectory: rawDirectory,
|
||||
descriptorDirectory: RAW_DIRECTORY_PATH,
|
||||
grammar: RAW_STAGING_PATTERN,
|
||||
label: "raw staging",
|
||||
}, failures)
|
||||
: undefined;
|
||||
const sealedAuthority = sealedDescriptor && evidenceDirectoryValid
|
||||
? capturePrivateAlias({
|
||||
descriptorMetadata: sealedDescriptor,
|
||||
descriptorTarget: SEALED_TEMP_FD_PATH,
|
||||
expectedDirectory: evidenceDirectory,
|
||||
descriptorDirectory: EVIDENCE_DIRECTORY_PATH,
|
||||
grammar: SEALED_TEMP_PATTERN,
|
||||
label: "sealed temp",
|
||||
}, failures)
|
||||
: undefined;
|
||||
|
||||
if (!rawAuthority || !sealedAuthority) {
|
||||
return await failBootstrap(rawAuthority, sealedAuthority, failures);
|
||||
}
|
||||
try {
|
||||
if (
|
||||
rawAuthority.stem !== sealedAuthority.stem ||
|
||||
rawAuthority.noncePrefix !== sealedAuthority.noncePrefix
|
||||
) {
|
||||
throw new TypeError("provider guardian inherited private aliases disagree");
|
||||
}
|
||||
const kind = providerKindFromStem(rawAuthority.stem);
|
||||
const rawLeaf = kind === "vulnerability"
|
||||
? "vulnerability-report.json"
|
||||
: "provenance-attestation.json";
|
||||
return Object.freeze({
|
||||
kind,
|
||||
noncePrefix: rawAuthority.noncePrefix,
|
||||
rawStagingLeaf: rawAuthority.leaf,
|
||||
rawStagingPath: rawAuthority.path,
|
||||
rawPath: `${RAW_DIRECTORY_PATH}/${rawLeaf}`,
|
||||
rawIdentity: rawAuthority.identity,
|
||||
sealedTempLeaf: sealedAuthority.leaf,
|
||||
sealedTempPath: sealedAuthority.path,
|
||||
sealedPath: `${EVIDENCE_DIRECTORY_PATH}/${rawLeaf}`,
|
||||
sealedIdentity: sealedAuthority.identity,
|
||||
});
|
||||
} catch (error) {
|
||||
failures.push(toError(error));
|
||||
return await failBootstrap(rawAuthority, sealedAuthority, failures);
|
||||
}
|
||||
}
|
||||
|
||||
function captureInheritedDirectory(
|
||||
fd: number,
|
||||
canonicalPath: string,
|
||||
label: string,
|
||||
failures: Error[],
|
||||
): boolean {
|
||||
try {
|
||||
assertInheritedDirectory(fd, canonicalPath, label);
|
||||
return true;
|
||||
} catch (error) {
|
||||
failures.push(toError(error));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function capturePrivateDescriptor(
|
||||
fd: number,
|
||||
label: string,
|
||||
failures: Error[],
|
||||
): Stats | undefined {
|
||||
try {
|
||||
return fstatSync(fd);
|
||||
} catch (error) {
|
||||
failures.push(new Error(`provider guardian inherited ${label} fd is invalid`, {
|
||||
cause: error,
|
||||
}));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function capturePrivateAlias(
|
||||
input: Readonly<{
|
||||
descriptorMetadata: Stats;
|
||||
descriptorTarget: string;
|
||||
expectedDirectory: string;
|
||||
descriptorDirectory: string;
|
||||
grammar: RegExp;
|
||||
label: string;
|
||||
}>,
|
||||
failures: Error[],
|
||||
): BoundPrivateAuthority | undefined {
|
||||
try {
|
||||
return bindPrivateAlias(input);
|
||||
} catch (error) {
|
||||
failures.push(toError(error));
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function bindPrivateAlias(input: Readonly<{
|
||||
descriptorMetadata: Stats;
|
||||
descriptorTarget: string;
|
||||
expectedDirectory: string;
|
||||
descriptorDirectory: string;
|
||||
grammar: RegExp;
|
||||
label: string;
|
||||
}>): BoundPrivateAuthority {
|
||||
const descriptorTarget = readlinkSync(input.descriptorTarget);
|
||||
if (path.dirname(descriptorTarget) !== input.expectedDirectory) {
|
||||
throw new TypeError(
|
||||
`provider guardian inherited ${input.label} alias is outside its directory`,
|
||||
);
|
||||
}
|
||||
const leaf = path.basename(descriptorTarget);
|
||||
const match = input.grammar.exec(leaf);
|
||||
if (!match) {
|
||||
throw new TypeError(`provider guardian inherited ${input.label} alias is invalid`);
|
||||
}
|
||||
const boundPath = `${input.descriptorDirectory}/${leaf}`;
|
||||
const pathnameMetadata = lstatSync(boundPath);
|
||||
assertPrivateMetadata(input.descriptorMetadata, pathnameMetadata, input.label);
|
||||
return Object.freeze({
|
||||
identity: Object.freeze({
|
||||
dev: input.descriptorMetadata.dev,
|
||||
ino: input.descriptorMetadata.ino,
|
||||
}),
|
||||
leaf,
|
||||
noncePrefix: match[2]!,
|
||||
path: boundPath,
|
||||
stem: match[1]!,
|
||||
});
|
||||
}
|
||||
|
||||
async function failBootstrap(
|
||||
rawAuthority: BoundPrivateAuthority | undefined,
|
||||
sealedAuthority: BoundPrivateAuthority | undefined,
|
||||
failures: Error[],
|
||||
): Promise<never> {
|
||||
for (const authority of [rawAuthority, sealedAuthority]) {
|
||||
if (!authority) continue;
|
||||
await cleanupOwnedPath(authority.path, authority.identity, failures);
|
||||
}
|
||||
closePrivateFds(failures);
|
||||
writeAggregateDiagnostic("provider guardian bootstrap failed", failures);
|
||||
process.exit(126);
|
||||
}
|
||||
|
||||
function assertPrivateMetadata(
|
||||
descriptorMetadata: Stats,
|
||||
pathnameMetadata: Stats,
|
||||
label: string,
|
||||
): void {
|
||||
if (
|
||||
!descriptorMetadata.isFile() || !pathnameMetadata.isFile() ||
|
||||
pathnameMetadata.isSymbolicLink() || descriptorMetadata.dev !== pathnameMetadata.dev ||
|
||||
descriptorMetadata.ino !== pathnameMetadata.ino || descriptorMetadata.nlink !== 1 ||
|
||||
pathnameMetadata.nlink !== 1 || (descriptorMetadata.mode & 0o777) !== 0o600 ||
|
||||
(pathnameMetadata.mode & 0o777) !== 0o600 || descriptorMetadata.size !== 0 ||
|
||||
pathnameMetadata.size !== 0
|
||||
) {
|
||||
throw new TypeError(`provider guardian inherited ${label} identity is invalid`);
|
||||
}
|
||||
}
|
||||
|
||||
function providerKindFromStem(stem: string): ProviderGuardianKind {
|
||||
if (stem === "vulnerability-report") return "vulnerability";
|
||||
if (stem === "provenance-attestation") return "provenance";
|
||||
throw new TypeError("provider guardian inherited private kind is invalid");
|
||||
}
|
||||
|
||||
function consumeChunk(chunk: Buffer | string): void {
|
||||
if (terminal) 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_PROVIDER_GUARDIAN_FRAME_PAYLOAD_BYTES) {
|
||||
enqueue(async () => failClosed(126, "provider guardian frame length is invalid"));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (expectedBytes !== undefined && pending.byteLength === expectedBytes + 4) {
|
||||
const payload = pending.subarray(4);
|
||||
pending = Buffer.alloc(0);
|
||||
expectedBytes = undefined;
|
||||
enqueue(async () => handleFrame(payload));
|
||||
} else if (expectedBytes !== undefined && pending.byteLength > expectedBytes + 4) {
|
||||
enqueue(async () => failClosed(126, "provider guardian frame has trailing bytes"));
|
||||
}
|
||||
}
|
||||
|
||||
function enqueue(operation: () => Promise<void>): void {
|
||||
operations = operations.then(operation).catch(async (error) => {
|
||||
await failClosed(126, error instanceof Error ? error.message : String(error));
|
||||
});
|
||||
}
|
||||
|
||||
async function handleFrame(payload: Buffer): Promise<void> {
|
||||
if (state === "starting") {
|
||||
await establishTransaction(payload);
|
||||
} else if (state === "guarding") {
|
||||
await publishSealedArtifact(payload);
|
||||
} else if (state === "published") {
|
||||
await prepareCommit(payload);
|
||||
} else {
|
||||
await failClosed(126, "provider guardian received data after commit");
|
||||
}
|
||||
}
|
||||
|
||||
async function establishTransaction(payload: Buffer): Promise<void> {
|
||||
const nowEpochMs = Date.now();
|
||||
const guard = decodeProviderGuardianGuard(payload, {
|
||||
nowEpochMs,
|
||||
maxLeaseMs: MAX_PROVIDER_GUARDIAN_LEASE_MS,
|
||||
});
|
||||
if (
|
||||
guard.kind !== bootstrap.kind ||
|
||||
guard.nonce.subarray(0, 16).toString("hex") !== bootstrap.noncePrefix ||
|
||||
providerGuardianRawStagingLeaf(guard.kind, guard.nonce) !== bootstrap.rawStagingLeaf ||
|
||||
providerGuardianSealedTempLeaf(guard.kind, guard.nonce) !== bootstrap.sealedTempLeaf
|
||||
) {
|
||||
throw new TypeError("provider guardian guard does not match inherited private aliases");
|
||||
}
|
||||
assertBoundPrivateLeaf(
|
||||
RAW_STAGING_FD,
|
||||
bootstrap.rawStagingPath,
|
||||
bootstrap.rawIdentity,
|
||||
"raw staging",
|
||||
);
|
||||
assertBoundPrivateLeaf(
|
||||
SEALED_TEMP_FD,
|
||||
bootstrap.sealedTempPath,
|
||||
bootstrap.sealedIdentity,
|
||||
"sealed temp",
|
||||
);
|
||||
transaction = Object.freeze({ guard });
|
||||
|
||||
await link(bootstrap.rawStagingPath, bootstrap.rawPath);
|
||||
assertOwnedPathMetadata(bootstrap.rawStagingPath, bootstrap.rawIdentity, 2, 0o600, 0,
|
||||
"raw staging link");
|
||||
assertOwnedPathMetadata(bootstrap.rawPath, bootstrap.rawIdentity, 2, 0o600, 0,
|
||||
"canonical raw link");
|
||||
await unlink(bootstrap.rawStagingPath);
|
||||
fsyncSync(RAW_DIRECTORY_FD);
|
||||
assertOwnedPathMetadata(bootstrap.rawPath, bootstrap.rawIdentity, 1, 0o600, 0,
|
||||
"canonical raw");
|
||||
assertBoundPrivateLeaf(
|
||||
SEALED_TEMP_FD,
|
||||
bootstrap.sealedTempPath,
|
||||
bootstrap.sealedIdentity,
|
||||
"sealed temp",
|
||||
);
|
||||
|
||||
const remainingLeaseMs = guard.deadlineEpochMs - Date.now();
|
||||
if (remainingLeaseMs <= 0) {
|
||||
throw new TypeError("provider guardian guard deadline expired during startup");
|
||||
}
|
||||
deadline = setTimeout(() => {
|
||||
enqueue(async () => failClosed(124, "provider guardian lease deadline expired", true));
|
||||
}, remainingLeaseMs);
|
||||
writeSync(1, encodeProviderGuardianReady({
|
||||
nonce: guard.nonce,
|
||||
rawDev: bootstrap.rawIdentity.dev,
|
||||
rawIno: bootstrap.rawIdentity.ino,
|
||||
sealedTempLeaf: bootstrap.sealedTempLeaf,
|
||||
sealedDev: bootstrap.sealedIdentity.dev,
|
||||
sealedIno: bootstrap.sealedIdentity.ino,
|
||||
}));
|
||||
state = "guarding";
|
||||
}
|
||||
|
||||
function assertBoundPrivateLeaf(
|
||||
fd: number,
|
||||
target: string,
|
||||
identity: OwnedIdentity,
|
||||
label: string,
|
||||
): void {
|
||||
const descriptorMetadata = fstatSync(fd);
|
||||
const pathnameMetadata = lstatSync(target);
|
||||
assertOwnedMetadata(descriptorMetadata, identity, 1, 0o600, 0, label);
|
||||
assertOwnedMetadata(pathnameMetadata, identity, 1, 0o600, 0, label);
|
||||
if (pathnameMetadata.isSymbolicLink()) {
|
||||
throw new TypeError(`provider guardian ${label} alias became symbolic`);
|
||||
}
|
||||
}
|
||||
|
||||
async function publishSealedArtifact(payload: Buffer): Promise<void> {
|
||||
if (!transaction) {
|
||||
throw new Error("provider guardian transaction identity is unavailable");
|
||||
}
|
||||
const publication = decodeProviderGuardianPublish(payload, transaction.guard.nonce);
|
||||
if (
|
||||
publication.sealedDev !== bootstrap.sealedIdentity.dev ||
|
||||
publication.sealedIno !== bootstrap.sealedIdentity.ino
|
||||
) {
|
||||
throw new TypeError("provider guardian publish identity is invalid");
|
||||
}
|
||||
assertOwnedMetadata(
|
||||
fstatSync(SEALED_TEMP_FD),
|
||||
bootstrap.sealedIdentity,
|
||||
1,
|
||||
0o400,
|
||||
publication.size,
|
||||
"sealed publish descriptor",
|
||||
);
|
||||
const pathnameMetadata = await lstat(bootstrap.sealedTempPath);
|
||||
assertOwnedMetadata(
|
||||
pathnameMetadata,
|
||||
bootstrap.sealedIdentity,
|
||||
1,
|
||||
0o400,
|
||||
publication.size,
|
||||
"sealed publish pathname",
|
||||
);
|
||||
if (pathnameMetadata.isSymbolicLink()) {
|
||||
throw new TypeError("provider guardian sealed publish pathname became symbolic");
|
||||
}
|
||||
const actualSha256 = hashInheritedFile(SEALED_TEMP_FD, publication.size);
|
||||
if (actualSha256 !== publication.sha256) {
|
||||
throw new TypeError("provider guardian publish hash is invalid");
|
||||
}
|
||||
try {
|
||||
await lstat(bootstrap.sealedPath);
|
||||
throw new Error("provider guardian sealed output already exists");
|
||||
} catch (error) {
|
||||
if (!hasErrorCode(error, "ENOENT")) throw error;
|
||||
}
|
||||
await link(bootstrap.sealedTempPath, bootstrap.sealedPath);
|
||||
await unlink(bootstrap.sealedTempPath);
|
||||
fsyncSync(EVIDENCE_DIRECTORY_FD);
|
||||
const finalMetadata = await lstat(bootstrap.sealedPath);
|
||||
assertOwnedMetadata(
|
||||
finalMetadata,
|
||||
bootstrap.sealedIdentity,
|
||||
1,
|
||||
0o400,
|
||||
publication.size,
|
||||
"sealed final",
|
||||
);
|
||||
if (finalMetadata.isSymbolicLink()) {
|
||||
throw new TypeError("provider guardian sealed final became symbolic");
|
||||
}
|
||||
state = "published";
|
||||
writeSync(1, encodeProviderGuardianPublished({
|
||||
nonce: transaction.guard.nonce,
|
||||
sealedDev: bootstrap.sealedIdentity.dev,
|
||||
sealedIno: bootstrap.sealedIdentity.ino,
|
||||
}));
|
||||
}
|
||||
|
||||
function assertOwnedPathMetadata(
|
||||
target: string,
|
||||
identity: OwnedIdentity,
|
||||
expectedLinks: number,
|
||||
expectedMode: number,
|
||||
expectedSize: number,
|
||||
label: string,
|
||||
): void {
|
||||
const metadata = lstatSync(target);
|
||||
assertOwnedMetadata(metadata, identity, expectedLinks, expectedMode, expectedSize, label);
|
||||
if (metadata.isSymbolicLink()) {
|
||||
throw new TypeError(`provider guardian ${label} became symbolic`);
|
||||
}
|
||||
}
|
||||
|
||||
function assertOwnedMetadata(
|
||||
metadata: Stats,
|
||||
identity: OwnedIdentity,
|
||||
expectedLinks: number,
|
||||
expectedMode: number,
|
||||
expectedSize: number,
|
||||
label: string,
|
||||
): void {
|
||||
if (
|
||||
!metadata.isFile() || metadata.dev !== identity.dev || metadata.ino !== identity.ino ||
|
||||
metadata.nlink !== expectedLinks || (metadata.mode & 0o777) !== expectedMode ||
|
||||
metadata.size !== expectedSize
|
||||
) {
|
||||
throw new TypeError(`provider guardian ${label} metadata is invalid`);
|
||||
}
|
||||
}
|
||||
|
||||
function hashInheritedFile(fd: number, size: number): string {
|
||||
const digest = createHash("sha256");
|
||||
const buffer = Buffer.allocUnsafe(Math.min(65_536, size));
|
||||
let position = 0;
|
||||
while (position < size) {
|
||||
const requested = Math.min(buffer.byteLength, size - position);
|
||||
const bytesRead = readSync(fd, buffer, 0, requested, position);
|
||||
if (bytesRead <= 0) throw new Error("provider guardian sealed publish read was truncated");
|
||||
digest.update(buffer.subarray(0, bytesRead));
|
||||
position += bytesRead;
|
||||
}
|
||||
return digest.digest("hex");
|
||||
}
|
||||
|
||||
async function prepareCommit(payload: Buffer): Promise<void> {
|
||||
if (!transaction) throw new Error("provider guardian transaction identity is unavailable");
|
||||
decodeProviderGuardianCommit(payload, transaction.guard.nonce);
|
||||
const removedRaw = await cleanupOwnedProviderReport({
|
||||
reportPath: bootstrap.rawPath,
|
||||
reportDev: bootstrap.rawIdentity.dev,
|
||||
reportIno: bootstrap.rawIdentity.ino,
|
||||
});
|
||||
if (!removedRaw) throw new Error("provider guardian raw output disappeared before commit");
|
||||
state = "commitPending";
|
||||
}
|
||||
|
||||
async function succeedOnCommittedEof(): Promise<void> {
|
||||
const closeErrors: Error[] = [];
|
||||
closePrivateFds(closeErrors);
|
||||
if (closeErrors.length > 0) {
|
||||
await failClosed(
|
||||
126,
|
||||
"provider guardian private descriptor close failed",
|
||||
false,
|
||||
closeErrors,
|
||||
);
|
||||
return;
|
||||
}
|
||||
terminal = true;
|
||||
if (deadline) clearTimeout(deadline);
|
||||
liveness.removeAllListeners();
|
||||
liveness.destroy();
|
||||
closeControlInputBestEffort();
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async function failClosed(
|
||||
exitCode: number,
|
||||
message: string,
|
||||
forceSignal = false,
|
||||
priorErrors: readonly Error[] = [],
|
||||
): Promise<void> {
|
||||
if (terminal) return;
|
||||
terminal = true;
|
||||
if (deadline) clearTimeout(deadline);
|
||||
liveness.removeAllListeners();
|
||||
liveness.destroy();
|
||||
const failures = [new Error(message), ...priorErrors];
|
||||
await cleanupOwnedPath(bootstrap.rawStagingPath, bootstrap.rawIdentity, failures);
|
||||
await cleanupOwnedPath(bootstrap.rawPath, bootstrap.rawIdentity, failures);
|
||||
await cleanupOwnedPath(bootstrap.sealedTempPath, bootstrap.sealedIdentity, failures);
|
||||
await cleanupOwnedPath(bootstrap.sealedPath, bootstrap.sealedIdentity, failures);
|
||||
closePrivateFds(failures);
|
||||
closeControlInputBestEffort();
|
||||
writeAggregateDiagnostic("provider guardian failed", failures);
|
||||
if (forceSignal) {
|
||||
try {
|
||||
process.kill(process.pid, "SIGKILL");
|
||||
} finally {
|
||||
process.exit(exitCode);
|
||||
}
|
||||
}
|
||||
process.exit(exitCode);
|
||||
}
|
||||
|
||||
async function cleanupOwnedPath(
|
||||
target: string,
|
||||
identity: OwnedIdentity,
|
||||
errors: Error[],
|
||||
): Promise<void> {
|
||||
try {
|
||||
await cleanupOwnedProviderReport({
|
||||
reportPath: target,
|
||||
reportDev: identity.dev,
|
||||
reportIno: identity.ino,
|
||||
});
|
||||
} catch (error) {
|
||||
errors.push(toError(error));
|
||||
}
|
||||
}
|
||||
|
||||
function closePrivateFds(errors: Error[]): void {
|
||||
if (privateFdsClosed) return;
|
||||
privateFdsClosed = true;
|
||||
for (const fd of [RAW_STAGING_FD, SEALED_TEMP_FD]) {
|
||||
try {
|
||||
closeSync(fd);
|
||||
} catch (error) {
|
||||
errors.push(toError(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeControlInputBestEffort(): void {
|
||||
try {
|
||||
closeSync(0);
|
||||
} catch {
|
||||
// Terminal cleanup and the exit status must not depend on a diagnostic fd.
|
||||
}
|
||||
}
|
||||
|
||||
function writeAggregateDiagnostic(label: string, failures: readonly Error[]): void {
|
||||
const aggregate = failures.length > 1
|
||||
? new AggregateError(failures, label, { cause: failures[0] })
|
||||
: failures[0];
|
||||
const detail = aggregate instanceof AggregateError
|
||||
? aggregate.errors.map((error) => toError(error).message).join("; ")
|
||||
: aggregate?.message ?? label;
|
||||
try {
|
||||
writeSync(2, `${label}: ${detail}\n`);
|
||||
} catch {
|
||||
// A closed parent-side pipe must not convert fail-closed termination to exit 0.
|
||||
}
|
||||
}
|
||||
|
||||
function assertInheritedDirectory(fd: number, canonicalPath: string, label: string): void {
|
||||
const descriptorMetadata = fstatSync(fd);
|
||||
const pathMetadata = lstatSync(canonicalPath);
|
||||
if (
|
||||
!descriptorMetadata.isDirectory() || !pathMetadata.isDirectory() ||
|
||||
pathMetadata.isSymbolicLink() || descriptorMetadata.dev !== pathMetadata.dev ||
|
||||
descriptorMetadata.ino !== pathMetadata.ino
|
||||
) {
|
||||
throw new TypeError(`provider guardian inherited ${label} fd is not a directory`);
|
||||
}
|
||||
}
|
||||
|
||||
function toError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
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