fix(reference): reject FIFO replacements without blocking
Use the shared nonblocking, no-follow regular-file opener at the source-read boundary and report a nonregular replacement as changed. Keep descriptor identity and extent checks. The compiled subprocess regression timed out on the baseline and completes without a FIFO writer after this change. Reference reader failure, capacity, cancellation, and import checks pass.
This commit is contained in:
parent
6636045c9c
commit
f1c58cc53f
|
|
@ -1,8 +1,12 @@
|
|||
import type { BigIntStats } from "node:fs";
|
||||
import { constants } from "node:fs";
|
||||
import { open, type FileHandle } from "node:fs/promises";
|
||||
import type { FileHandle } from "node:fs/promises";
|
||||
import { dirname } from "node:path";
|
||||
|
||||
import {
|
||||
NonRegularFileReadError,
|
||||
openRegularFile,
|
||||
} from "../filesystem/RegularFile.js";
|
||||
|
||||
import {
|
||||
entryFailure,
|
||||
filesystemFailureDetail,
|
||||
|
|
@ -209,7 +213,7 @@ export const readStableFile = async (
|
|||
parentBefore.message,
|
||||
safeSize(expected.size),
|
||||
);
|
||||
handle = await open(absolute, constants.O_RDONLY | constants.O_NOFOLLOW);
|
||||
handle = await openRegularFile(absolute, { symlinks: "reject", signal });
|
||||
const prepared = await prepareFileRead(request, handle);
|
||||
if (prepared.status === "failed") return prepared.entry;
|
||||
const contents = await readFileContents({
|
||||
|
|
@ -240,6 +244,14 @@ export const readStableFile = async (
|
|||
"File read cancelled",
|
||||
safeSize(expected.size),
|
||||
);
|
||||
if (cause instanceof NonRegularFileReadError)
|
||||
return entryFailure(
|
||||
path,
|
||||
"file",
|
||||
"changed",
|
||||
"File is no longer a regular file",
|
||||
safeSize(expected.size),
|
||||
);
|
||||
const message = filesystemFailureDetail(
|
||||
cause,
|
||||
"File could not be read safely",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import { expect } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { processTest } from "../../support/process/processFixture.js";
|
||||
|
||||
processTest.skipIf(process.platform === "win32")(
|
||||
"rejects a FIFO replacing an admitted source file without waiting for a writer",
|
||||
async ({ processes }) => {
|
||||
const root = await createTestTempDirectory("rea-reference-fifo-race-");
|
||||
const probe = fileURLToPath(
|
||||
new URL("../../fixtures/referenceReaderFifoProbe.mjs", import.meta.url),
|
||||
);
|
||||
const readerModule = fileURLToPath(
|
||||
new URL(
|
||||
"../../../dist/reference/ReferenceSourceReaderFile.js",
|
||||
import.meta.url,
|
||||
),
|
||||
);
|
||||
const result = await processes.run(
|
||||
process.execPath,
|
||||
[probe, readerModule, root],
|
||||
{ timeoutMs: 3_000 },
|
||||
);
|
||||
expect(result.exitCode, result.stderr).toBe(0);
|
||||
expect(result.stdout.trim()).toBe("changed");
|
||||
},
|
||||
);
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { lstat, unlink, writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const [modulePath, root] = process.argv.slice(2);
|
||||
if (modulePath === undefined || root === undefined)
|
||||
throw new Error("Reference reader FIFO probe requires module and root paths");
|
||||
|
||||
const { readStableFile } = await import(pathToFileURL(modulePath));
|
||||
const absolute = join(root, "source.txt");
|
||||
await writeFile(absolute, "observed regular file\n");
|
||||
const expected = await lstat(absolute, { bigint: true });
|
||||
await unlink(absolute);
|
||||
execFileSync("mkfifo", [absolute]);
|
||||
|
||||
// Capture the directory after replacement to reach the file-open boundary.
|
||||
// The owning test bounds this child if an O_RDONLY FIFO open blocks again.
|
||||
const rootIdentity = await lstat(root, { bigint: true });
|
||||
const result = await readStableFile({
|
||||
root,
|
||||
rootIdentity,
|
||||
absolute,
|
||||
path: "source.txt",
|
||||
expected,
|
||||
});
|
||||
assert.equal(result.status, "failed");
|
||||
assert.equal(result.kind, "file");
|
||||
assert.equal(result.code, "changed");
|
||||
process.stdout.write("changed\n");
|
||||
Loading…
Reference in New Issue