41 lines
1.4 KiB
TypeScript
41 lines
1.4 KiB
TypeScript
import { randomBytes } from "node:crypto";
|
|
import { lstat, rename, unlink } from "node:fs/promises";
|
|
|
|
export type ProviderRawIdentity = Readonly<{
|
|
reportPath: string;
|
|
reportDev: number;
|
|
reportIno: number;
|
|
}>;
|
|
|
|
export async function cleanupOwnedProviderReport(
|
|
identity: ProviderRawIdentity,
|
|
): Promise<boolean> {
|
|
try {
|
|
const metadata = await lstat(identity.reportPath);
|
|
if (!matchesReportIdentity(metadata, identity)) return false;
|
|
const quarantine = `${identity.reportPath}.parent-loss-${process.pid}-${randomBytes(16).toString("hex")}`;
|
|
await rename(identity.reportPath, quarantine);
|
|
const quarantinedMetadata = await lstat(quarantine);
|
|
if (!matchesReportIdentity(quarantinedMetadata, identity)) {
|
|
throw new Error("provider raw output identity changed during parent-loss cleanup");
|
|
}
|
|
await unlink(quarantine);
|
|
return true;
|
|
} catch (error) {
|
|
if (hasErrorCode(error, "ENOENT")) return false;
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function matchesReportIdentity(
|
|
metadata: Awaited<ReturnType<typeof lstat>>,
|
|
identity: ProviderRawIdentity,
|
|
): boolean {
|
|
return metadata.isFile() && !metadata.isSymbolicLink() &&
|
|
metadata.dev === identity.reportDev && metadata.ino === identity.reportIno;
|
|
}
|
|
|
|
function hasErrorCode(error: unknown, code: string): boolean {
|
|
return Boolean(error && typeof error === "object" && "code" in error && error.code === code);
|
|
}
|