From ee654e98a6fbde73072fb35d915f46c87486b0ce Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 08:10:55 +0800 Subject: [PATCH] refactor(artifacts): retain PyInstaller carrier dialect --- src/artifacts/PyInstallerArtifactReader.ts | 4 +- src/artifacts/inventory/classify.ts | 9 +- .../pyinstaller/PyInstallerAdmission.test.ts | 108 +++++++++++++++++- .../pyinstaller/PyInstallerAdmission.ts | 3 + 4 files changed, 121 insertions(+), 3 deletions(-) diff --git a/src/artifacts/PyInstallerArtifactReader.ts b/src/artifacts/PyInstallerArtifactReader.ts index aaf74fc3af..821bac62c1 100644 --- a/src/artifacts/PyInstallerArtifactReader.ts +++ b/src/artifacts/PyInstallerArtifactReader.ts @@ -323,7 +323,9 @@ export class PyInstallerArtifactReader implements ArtifactReader { await this.#openHandle(signal); const { handle, initial } = await this.#source.readSelectedPath(); const size = initial.size; - const pathDialect = await carrierPathDialect(handle, size, signal); + const pathDialect = + this.#admission?.carrierPathDialect ?? + (await carrierPathDialect(handle, size, signal)); const cookie = this.#admission?.cookie ?? (await this.#cookie(handle, size, signal)); this.#budget = artifactExpansionBudgetBytes(cookie.packageLength); diff --git a/src/artifacts/inventory/classify.ts b/src/artifacts/inventory/classify.ts index 6d7cdf89fd..a48c27aa68 100644 --- a/src/artifacts/inventory/classify.ts +++ b/src/artifacts/inventory/classify.ts @@ -302,8 +302,15 @@ const classifyRootFormat = async ( } const packaged = await embeddedProbes.pyInstaller(); abortIfNeeded(signal); + // A valid cookie also proves enough bytes for a thin Mach-O identity; + // PE and universal Mach-O classification already require stronger proof. if (packaged !== undefined) - return { kind: "pyinstaller", source, cookie: packaged }; + return { + kind: "pyinstaller", + source, + cookie: packaged, + carrierPathDialect: format === "pe" ? "windows" : "posix", + }; } return format; }; diff --git a/src/artifacts/pyinstaller/PyInstallerAdmission.test.ts b/src/artifacts/pyinstaller/PyInstallerAdmission.test.ts index 04d7ec7c10..d5353ab328 100644 --- a/src/artifacts/pyinstaller/PyInstallerAdmission.test.ts +++ b/src/artifacts/pyinstaller/PyInstallerAdmission.test.ts @@ -1,4 +1,4 @@ -import { appendFile, writeFile } from "node:fs/promises"; +import { appendFile, lstat, writeFile } from "node:fs/promises"; import { join } from "node:path"; import { expect, it, vi } from "vitest"; import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js"; @@ -6,6 +6,7 @@ import { withOwnedRegularFile } from "../../filesystem/OwnedRegularFile.js"; import { ArtifactEmbeddedFormatProbes } from "../ArtifactEmbeddedFormatProbes.js"; import { ArtifactResourceScope } from "../ArtifactResourceScope.js"; import { inventoryArtifact } from "../inventory/ArtifactInventory.js"; +import { classifyAndHashRootForInventory } from "../inventory/classify.js"; import { PyInstallerArtifactReader } from "../PyInstallerArtifactReader.js"; import { cArchiveExecutable } from "./CArchive.fixture.js"; import * as layout from "./CArchiveLayout.js"; @@ -25,6 +26,110 @@ const fixture = () => ], }); +it.each([ + { + carrier: "elf", + offset: undefined, + dialect: "posix", + path: String.raw`pkg\data.bin`, + }, + { carrier: "pe", offset: 64, dialect: "windows", path: "pkg/data.bin" }, + { carrier: "pe", offset: 8190, dialect: "windows", path: "pkg/data.bin" }, +] as const)( + "retains $carrier carrier dialect at offset $offset through member projection", + async ({ carrier, offset, dialect, path: memberPath }) => { + const root = await createTestTempDirectory( + "rea-pyinstaller-carrier-admission-", + ); + const path = join(root, "app"); + const sourceName = String.raw`pkg\data.bin`; + await writeFile( + path, + cArchiveExecutable({ + carrier, + ...(offset === undefined ? {} : { peHeaderOffset: offset }), + layout: "2.1", + pythonVersion: 314, + members: [ + { + name: sourceName, + typeCode: "x", + payload: Buffer.from("fixture"), + compress: false, + }, + ], + }), + ); + const resourceScope = new ArtifactResourceScope(); + try { + const classified = await classifyAndHashRootForInventory( + path, + false, + await lstat(path), + { resourceScope }, + ); + const admission = classified.readerAdmission; + if ( + admission === undefined || + !("kind" in admission) || + admission.kind !== "pyinstaller" + ) + throw new Error("Fixture did not produce a PyInstaller admission"); + expect(admission.carrierPathDialect).toBe(dialect); + const reader = new PyInstallerArtifactReader(path, admission); + try { + if (classified.digest === null) + throw new Error("Missing carrier digest"); + await reader.prepareContainer(classified.digest.sha256); + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + expect(entries).toEqual([ + expect.objectContaining({ + path: memberPath, + formatMetadata: expect.objectContaining({ + source_name: sourceName, + }), + }), + ]); + } finally { + await reader.close(); + } + } finally { + await resourceScope.close(); + } + }, +); + +it("preserves backslashes when a standalone archive has no recognized carrier", async () => { + const root = await createTestTempDirectory( + "rea-pyinstaller-unknown-carrier-", + ); + const path = join(root, "app"); + const sourceName = String.raw`pkg\data.bin`; + const bytes = cArchiveExecutable({ + layout: "2.1", + pythonVersion: 314, + members: [ + { + name: sourceName, + typeCode: "x", + payload: Buffer.from("fixture"), + compress: false, + }, + ], + }); + bytes.fill(0, 0, 64); + await writeFile(path, bytes); + const reader = new PyInstallerArtifactReader(path); + try { + const entries = []; + for await (const entry of reader.entries()) entries.push(entry); + expect(entries.map((entry) => entry.path)).toEqual([sourceName]); + } finally { + await reader.close(); + } +}); + it("inventories members using the cookie discovered by root classification", async () => { const root = await createTestTempDirectory( "rea-pyinstaller-cookie-inventory-", @@ -66,6 +171,7 @@ it("rejects changed source before consuming an admitted cookie and leaves the bo kind: "pyinstaller", source, cookie, + carrierPathDialect: "posix", }); await appendFile(path, "changed"); try { diff --git a/src/artifacts/pyinstaller/PyInstallerAdmission.ts b/src/artifacts/pyinstaller/PyInstallerAdmission.ts index b4f8b4d783..ad30048ac6 100644 --- a/src/artifacts/pyinstaller/PyInstallerAdmission.ts +++ b/src/artifacts/pyinstaller/PyInstallerAdmission.ts @@ -1,9 +1,12 @@ import type { StableRegularFileDescriptor } from "../../filesystem/RegularFile.js"; import type { PyInstallerCookie } from "./CArchiveLayout.js"; +import type { CarrierPathDialect } from "./CArchiveMembers.js"; /** Validated cookie facts borrowed with the descriptor that produced them. */ export interface PyInstallerAdmission { readonly kind: "pyinstaller"; readonly source: StableRegularFileDescriptor; readonly cookie: PyInstallerCookie; + /** Carrier interpretation retained by the classifier that admitted this source. */ + readonly carrierPathDialect?: CarrierPathDialect; }