Files
rea/src/filesystem/readFileHandleChunks.ts
T
morluto 918713af37 fix(artifacts): bind directory reads to captured file state
Capture exact filesystem identity and nanosecond timestamps, validate descriptor/path state before and after streaming, and enforce the captured byte count. Bound growing files to their captured size plus one byte using precise positional reads; keep unrepresentable display sizes explicitly unknown.

Reuse the captured-state comparison around Apple assetutil and retain existing reader cleanup ownership. Validation: focused reader and affected consumer suites, full static/architecture checks, and a compiled directory-read probe on Bun 1.3.14 passed. Includes same-size mutation, growth, truncation, cancellation, FIFO replacement, cleanup retry, ZIP identity forgery, ASAR and Mach-O coverage.
2026-10-12 04:45:17 +08:00

56 lines
1.8 KiB
TypeScript

import type { FileHandle } from "node:fs/promises";
import { Readable } from "node:stream";
/** Stream positional reads while leaving descriptor cleanup to its owner. */
export const readFileHandleChunks = (
handle: Pick<FileHandle, "read">,
options: {
readonly start?: number | bigint;
readonly end?: number | bigint;
readonly signal?: AbortSignal | undefined;
} = {},
): Readable => {
const start = fileOffset(options.start ?? 0);
const unlimited = options.end === undefined || options.end === Infinity;
const end = unlimited ? undefined : fileOffset(options.end);
if (
start === undefined ||
(!unlimited && end === undefined) ||
(end !== undefined && end < start)
)
throw new RangeError("Invalid file read range");
return Readable.from(
(async function* () {
let position = start;
while (end === undefined || position <= end) {
options.signal?.throwIfAborted();
const remaining = end === undefined ? undefined : end - position + 1n;
const chunk = Buffer.allocUnsafe(
remaining === undefined || remaining > 64n * 1024n
? 64 * 1024
: Number(remaining),
);
const { bytesRead } = await handle.read(
chunk,
0,
chunk.length,
position,
);
options.signal?.throwIfAborted();
if (bytesRead === 0) return;
position += BigInt(bytesRead);
yield chunk.subarray(0, bytesRead);
}
})(),
{
objectMode: false,
...(options.signal === undefined ? {} : { signal: options.signal }),
},
);
};
const fileOffset = (value: number | bigint): bigint | undefined => {
if (typeof value === "bigint") return value >= 0n ? value : undefined;
return Number.isSafeInteger(value) && value >= 0 ? BigInt(value) : undefined;
};