Files
rea/src/filesystem/RegularFile.ts
T
22e5aa97af feat(artifacts): inspect classic iNES container regions (#2816)
* feat(artifacts): inspect classic iNES container regions

* fix(artifacts): preserve iNES source disappearance as integrity failure

* fix(artifacts): revalidate iNES source before parsing header

* fix(artifacts): validate admitted roots before classification

* fix(artifacts): normalize source loss during root hashing

* fix(artifacts): bind iNES extraction admission to inventory

* test(artifacts): clarify non-executing inspection wording

* fix: preserve binary plist detection with extended magic probe

* test(browser): wait for popup document loads before capture

* fix(artifacts): honor cancellation across source validation

* fix(artifacts): prefer artifact magic over implicit database suffix

* fix(artifacts): select extraction readers from scanned format

* fix(artifacts): complete prefix reads and share ZIP format admission

* fix(browser): budget scenario cleanup for content drain and shutdown

* test(artifacts): admit valid universal inputs before routing

* test(artifacts): verify iNES workflows on macOS

* test(ci): own iNES macOS coverage and portable fixture paths

* fix: preserve cleanup retries and avoid duplicate inventory parsing

* fix(contracts): share ZIP target formats across MCP lifecycle schemas

* fix(target): preserve typed cancellation during descriptor admission

* refactor(artifacts): use JSON-safe inventory producer output directly

* test(artifacts): share file read setup and satisfy CI checks

* fix(artifacts): classify changed admission as integrity failure

* ci: run Mach-O summary delivery coverage on macOS

* test(javascript): align parameter provenance with main contract

* test(browser): await discoverable fixture pages before runtime verification

* fix(artifacts): pass target metadata to format detection

* fix(artifacts): bind extraction to opened target

* fix: repair the static-check failures on the default branch (#2895)

* fix: repair the static-check failures on the default branch

The default branch fails its own required `Static checks` job three ways, so
every pull request reports that job as failed regardless of its own change.

- `npm run format:check` reports `src/domain/functionComparison.test.ts` and
  `src/ida/IdaOperationRunner.ts`.
- `npm run lint` reports a duplicate `import { join } from "node:path"` in
  `tests/boundary/mcp/ghidraInventoryMcp.test.ts` (lines 4 and 17).
- `npm run typecheck` reports `format: "devin"` missing from the client format
  union in `src/application/SupportedClients.ts`, and an unchecked index in
  `tests/boundary/cli/clientProfileLifecycle.test.ts`, whose pair array is
  inferred as `string[][]`.

Apply the pinned formatter, drop the duplicate import, list the `"devin"` format
that its own definition already uses, and mark the pair array `as const`.

Validation: `npm run check:fast` and `npm run format:check` pass.

* fix(setup): keep Devin on the JSON configuration dialect

---------

Co-authored-by: 吴佳浩 <[email protected]>
Co-authored-by: morluto <[email protected]>
(cherry picked from commit df3d96da80)

* fix(artifacts): bind session inventories to target identity

* test(browser): bound fixture readiness acknowledgements

---------

Co-authored-by: 64579525 <[email protected]>
Co-authored-by: 吴佳浩Alben <[email protected]>
Co-authored-by: 吴佳浩 <[email protected]>
2026-10-31 15:49:16 +08:00

243 lines
7.9 KiB
TypeScript

import { constants } from "node:fs";
import { constants as osConstants } from "node:os";
import type { BigIntStats, Stats } from "node:fs";
import { lstat, open, stat, type FileHandle } from "node:fs/promises";
import { OwnedFileHandle } from "./OwnedFileHandle.js";
/** Cancellation observed at a file operation's explicit checkpoint. */
export class RegularFileOperationCancelled extends Error {
readonly _tag = "RegularFileOperationCancelled";
constructor(readonly reason: unknown) {
super("Regular file operation cancelled", { cause: reason });
this.name = this._tag;
}
}
export const assertRegularFileActive = (signal?: AbortSignal): void => {
if (signal?.aborted === true)
throw new RegularFileOperationCancelled(signal.reason);
};
/** Identifies a selected path that cannot be read as one regular file. */
export class NonRegularFileReadError extends Error {
readonly code: "EISDIR" | "ENOTFILE";
constructor(
readonly path: string,
directory: boolean,
) {
super(`Selected input must be a regular file: ${path}`);
this.name = "NonRegularFileReadError";
this.code = directory ? "EISDIR" : "ENOTFILE";
}
}
/** Admission failed and the opened descriptor could not be confirmed closed. */
export class RegularFileAdmissionFailure extends Error {
override readonly cause: unknown;
constructor(
readonly path: string,
admissionCause: unknown,
readonly cleanupCause: unknown,
readonly owner: OwnedFileHandle,
) {
super(
`Selected input failed admission and its descriptor close failed: ${path}`,
);
this.name = "RegularFileAdmissionFailure";
this.cause = admissionCause;
}
}
/** A regular input no longer matches the extent or identity admitted for reading. */
export class RegularFileChangedError extends Error {
readonly code = "ESTALE";
constructor(
readonly path: string,
options?: ErrorOptions,
) {
super(`Selected input changed while being read: ${path}`, options);
this.name = "RegularFileChangedError";
}
}
const SELECTED_REGULAR_FILE_ERROR_CODES = new Set([
"ENOENT",
"ENOTDIR",
"ELOOP",
"ENAMETOOLONG",
"EISDIR",
"ENOTFILE",
"ESTALE",
]);
/** Whether an errno identifies the selected path or file itself. */
export const isSelectedRegularFileError = (
cause: unknown,
): cause is Error & { readonly code: string } => {
if (!(cause instanceof Error)) return false;
const code: unknown = Reflect.get(cause, "code");
return (
typeof code === "string" && SELECTED_REGULAR_FILE_ERROR_CODES.has(code)
);
};
/** One admitted file descriptor and the metadata that identified its contents. */
export interface StableRegularFileDescriptor {
readonly handle: FileHandle;
readonly initial: Stats;
}
/** Admit a non-blocking regular-file descriptor with its validated initial facts. */
export const openRegularFile = async (
path: string,
options: {
readonly symlinks: "follow" | "reject";
readonly signal?: AbortSignal | undefined;
},
): Promise<StableRegularFileDescriptor> => {
assertRegularFileActive(options.signal);
const handle = await open(
path,
constants.O_RDONLY |
(constants.O_NONBLOCK ?? 0) |
(constants.O_NOCTTY ?? 0) |
(options.symlinks === "reject" ? (constants.O_NOFOLLOW ?? 0) : 0),
).catch(async (cause: unknown) => {
const nonRegular = await nonRegularOpenFailure(
path,
options.symlinks,
cause,
);
if (nonRegular === undefined) throw cause;
throw nonRegular;
});
const owner = new OwnedFileHandle(handle);
try {
assertRegularFileActive(options.signal);
const metadata = await handle.stat();
if (!metadata.isFile())
throw new NonRegularFileReadError(path, metadata.isDirectory());
assertRegularFileActive(options.signal);
// O_NOFOLLOW is unavailable on Windows. Recheck the name only on hosts
// where open could have followed a symlink, while the descriptor is owned.
if (options.symlinks === "reject" && (constants.O_NOFOLLOW ?? 0) === 0) {
const named = await lstat(path);
if (named.isSymbolicLink())
throw new NonRegularFileReadError(path, false);
if (!sameRegularFileState(metadata, named))
throw new RegularFileChangedError(path);
assertRegularFileActive(options.signal);
}
return { handle, initial: metadata };
} catch (cause: unknown) {
try {
await owner.close();
} catch (cleanupCause: unknown) {
throw new RegularFileAdmissionFailure(path, cause, cleanupCause, owner);
}
throw cause;
}
};
/**
* Some non-regular files cannot be opened at all: a Unix socket fails with
* ENXIO on Linux and EOPNOTSUPP on macOS. Confirm such a failure against the
* path's own metadata before reporting a non-regular selection.
*/
const nonRegularOpenFailure = async (
path: string,
symlinks: "follow" | "reject",
cause: unknown,
): Promise<NonRegularFileReadError | undefined> => {
// Only errors meaning "this file type cannot be opened" are reclassified;
// permission, symlink-rejection and resource failures keep their codes.
if (!isUnopenableFileTypeError(cause)) return undefined;
const metadata = await (symlinks === "reject" ? lstat : stat)(path).catch(
() => undefined,
);
if (metadata === undefined || metadata.isFile()) return undefined;
return new NonRegularFileReadError(path, metadata.isDirectory());
};
// Match by errno: libuv has no name for macOS EOPNOTSUPP (102) and reports
// "Unknown system error -102" as its code.
const errnos: Partial<Record<string, number>> = osConstants.errno;
const UNOPENABLE_FILE_TYPE_ERRNOS: ReadonlySet<number> = new Set(
[errnos.ENXIO, errnos.EOPNOTSUPP, errnos.ENOTSUP].flatMap((errno) =>
errno === undefined ? [] : [-errno],
),
);
const isUnopenableFileTypeError = (cause: unknown): boolean =>
cause instanceof Error &&
"errno" in cause &&
typeof cause.errno === "number" &&
UNOPENABLE_FILE_TYPE_ERRNOS.has(cause.errno);
/** Stable content identity; access times are deliberately excluded. */
export type RegularFileState = Pick<
Stats,
"dev" | "ino" | "size" | "mode" | "mtimeMs" | "ctimeMs"
>;
export const regularFileState = (stats: Stats): RegularFileState => ({
dev: stats.dev,
ino: stats.ino,
size: stats.size,
mode: stats.mode,
mtimeMs: stats.mtimeMs,
ctimeMs: stats.ctimeMs,
});
/** Compare one regular file's identity and mutable metadata across a read. */
export const sameRegularFileState = (
expected: RegularFileState | BigIntStats,
observed: Stats | BigIntStats,
): boolean =>
observed.isFile() &&
expected.dev === observed.dev &&
expected.ino === observed.ino &&
expected.size === observed.size &&
expected.mode === observed.mode &&
("mtimeNs" in expected && "mtimeNs" in observed
? expected.mtimeNs === observed.mtimeNs &&
expected.ctimeNs === observed.ctimeNs
: expected.mtimeMs === observed.mtimeMs &&
expected.ctimeMs === observed.ctimeMs);
/** Validate an admitted descriptor and selected name at the same precision as admission. */
export const assertStableRegularFileIdentity = async (
file: FileHandle,
path: string,
before: Stats | BigIntStats,
options: {
readonly symlinks: "follow" | "reject";
readonly signal?: AbortSignal;
},
): Promise<void> => {
assertRegularFileActive(options.signal);
const bigint = typeof before.dev === "bigint";
const named = async () => {
try {
const lookup = options.symlinks === "follow" ? stat : lstat;
return bigint ? await lookup(path, { bigint: true }) : await lookup(path);
} catch (cause: unknown) {
if (isSelectedRegularFileError(cause))
throw new RegularFileChangedError(path, { cause });
throw cause;
}
};
const selected = await named();
assertRegularFileActive(options.signal);
const opened = await file.stat({ bigint });
assertRegularFileActive(options.signal);
if (
!sameRegularFileState(before, opened) ||
!sameRegularFileState(before, selected)
)
throw new RegularFileChangedError(path);
};