mirror of
https://github.com/oven-sh/bun
synced 2026-03-12 10:17:45 +01:00
Compare commits
3 Commits
spawnsync-
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
113dd0496b | ||
|
|
5296e991fe | ||
|
|
40eb697cf8 |
@@ -546,27 +546,13 @@ pub fn spawnMaybeSync(
|
||||
else
|
||||
jsc_vm.eventLoop();
|
||||
|
||||
// Save the original event loop handle on THIS stack frame. The singleton's
|
||||
// `original_event_loop_handle` field is not nesting-safe: if a queued JS
|
||||
// callback runs during sync_loop.tickWithTimeout() and calls spawnSync again,
|
||||
// the nested prepare() overwrites the singleton field with the already-
|
||||
// overridden handle, and both cleanups then restore the wrong value. Saving
|
||||
// on each caller's stack frame means LIFO defer order restores correctly.
|
||||
const saved_event_loop_handle = if (comptime is_sync) jsc_vm.event_loop_handle else {};
|
||||
const saved_event_loop_ptr = if (comptime is_sync) jsc_vm.event_loop else {};
|
||||
|
||||
if (comptime is_sync) {
|
||||
jsc_vm.rareData().spawnSyncEventLoop(jsc_vm).prepare(jsc_vm);
|
||||
}
|
||||
|
||||
defer {
|
||||
if (comptime is_sync) {
|
||||
// Call cleanup first for its other bookkeeping (Windows timer stop).
|
||||
// Its handle restore may be wrong if nesting occurred — we overwrite it below.
|
||||
jsc_vm.rareData().spawnSyncEventLoop(jsc_vm).cleanup(jsc_vm, saved_event_loop_ptr);
|
||||
// Authoritative restore from OUR stack frame, not the (possibly corrupted) singleton.
|
||||
jsc_vm.event_loop_handle = saved_event_loop_handle;
|
||||
jsc_vm.event_loop = saved_event_loop_ptr;
|
||||
jsc_vm.rareData().spawnSyncEventLoop(jsc_vm).cleanup(jsc_vm, jsc_vm.eventLoop());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,13 +31,6 @@ original_event_loop_handle: @FieldType(jsc.VirtualMachine, "event_loop_handle")
|
||||
uv_timer: if (bun.Environment.isWindows) ?*bun.windows.libuv.Timer else void = if (bun.Environment.isWindows) null else {},
|
||||
did_timeout: bool = false,
|
||||
|
||||
/// Reentrancy guard. spawnSync can be called recursively if a queued JS
|
||||
/// callback (e.g. an async subprocess's completion handler) runs during
|
||||
/// tickWithTimeout and itself calls spawnSync. Without this counter, the
|
||||
/// nested prepare() would overwrite original_event_loop_handle with the
|
||||
/// already-overridden value, and both cleanups would restore the wrong loop.
|
||||
nesting_depth: u32 = 0,
|
||||
|
||||
/// Minimal handler for the isolated loop
|
||||
const Handler = struct {
|
||||
pub fn wakeup(loop: *uws.Loop) callconv(.c) void {
|
||||
@@ -102,22 +95,12 @@ pub fn prepare(this: *SpawnSyncEventLoop, vm: *jsc.VirtualMachine) void {
|
||||
this.did_timeout = false;
|
||||
this.event_loop.virtual_machine = vm;
|
||||
|
||||
// Only save/override on the outermost call. Nested calls are no-ops
|
||||
// because the isolated loop is already active.
|
||||
defer this.nesting_depth += 1;
|
||||
if (this.nesting_depth > 0) return;
|
||||
|
||||
this.original_event_loop_handle = vm.event_loop_handle;
|
||||
vm.event_loop_handle = if (bun.Environment.isPosix) this.uws_loop else this.uws_loop.uv_loop;
|
||||
}
|
||||
|
||||
/// Restore the original event loop handle after spawnSync completes
|
||||
pub fn cleanup(this: *SpawnSyncEventLoop, vm: *jsc.VirtualMachine, prev_event_loop: *jsc.EventLoop) void {
|
||||
// Only restore on the outermost call. Inner cleanups skip restoration
|
||||
// so the outer spawnSync keeps running on the isolated loop.
|
||||
this.nesting_depth -= 1;
|
||||
if (this.nesting_depth > 0) return;
|
||||
|
||||
vm.event_loop_handle = this.original_event_loop_handle;
|
||||
vm.event_loop = prev_event_loop;
|
||||
|
||||
|
||||
@@ -654,7 +654,11 @@ DEFINE_NATIVE_MODULE(NodeConstants)
|
||||
#ifdef O_EXCL
|
||||
put(Identifier::fromString(vm, "O_EXCL"_s), jsNumber(O_EXCL));
|
||||
#endif
|
||||
#if OS(WINDOWS)
|
||||
put(Identifier::fromString(vm, "UV_FS_O_FILEMAP"_s), jsNumber(536870912));
|
||||
#else
|
||||
put(Identifier::fromString(vm, "UV_FS_O_FILEMAP"_s), jsNumber(0));
|
||||
#endif
|
||||
|
||||
#ifdef O_NOCTTY
|
||||
put(Identifier::fromString(vm, "O_NOCTTY"_s), jsNumber(O_NOCTTY));
|
||||
|
||||
@@ -1053,7 +1053,15 @@ pub const FileSystemFlags = enum(c_int) {
|
||||
return ctx.throwValue(ctx.ERR(.OUT_OF_RANGE, "The value of \"flags\" is out of range. It must be an integer. Received {d}", .{val.asNumber()}).toJS());
|
||||
}
|
||||
const number = try val.coerce(i32, ctx);
|
||||
return @as(FileSystemFlags, @enumFromInt(@max(number, 0)));
|
||||
const flags = @max(number, 0);
|
||||
// On Windows, numeric flags from fs.constants (e.g. O_CREAT=0x100)
|
||||
// use the platform's native MSVC/libuv values which differ from the
|
||||
// internal bun.O representation. Convert them here so downstream
|
||||
// code that operates on bun.O flags works correctly.
|
||||
if (comptime bun.Environment.isWindows) {
|
||||
return @as(FileSystemFlags, @enumFromInt(bun.windows.libuv.O.toBunO(flags)));
|
||||
}
|
||||
return @as(FileSystemFlags, @enumFromInt(flags));
|
||||
}
|
||||
|
||||
const jsType = val.jsType();
|
||||
|
||||
@@ -190,22 +190,67 @@ pub const O = struct {
|
||||
pub const SYMLINK = UV_FS_O_SYMLINK;
|
||||
pub const SYNC = UV_FS_O_SYNC;
|
||||
|
||||
/// Convert from internal bun.O flags to libuv/Windows flags.
|
||||
///
|
||||
/// Note: NONBLOCK, NOFOLLOW, DIRECTORY, NOATIME, NOCTTY, SYMLINK map to
|
||||
/// 0 in libuv on Windows (see UV_FS_O_* constants below), so they are
|
||||
/// included here for correctness but are effectively no-ops.
|
||||
/// When adding new flag mappings, keep in sync with toBunO.
|
||||
pub fn fromBunO(c_flags: i32) i32 {
|
||||
var flags: i32 = 0;
|
||||
|
||||
if (c_flags & bun.O.NONBLOCK != 0) flags |= NONBLOCK;
|
||||
if (c_flags & bun.O.CREAT != 0) flags |= CREAT;
|
||||
if (c_flags & bun.O.NOFOLLOW != 0) flags |= NOFOLLOW;
|
||||
if (c_flags & bun.O.WRONLY != 0) flags |= WRONLY;
|
||||
if (c_flags & bun.O.RDONLY != 0) flags |= RDONLY;
|
||||
if (c_flags & bun.O.RDWR != 0) flags |= RDWR;
|
||||
if (c_flags & bun.O.CREAT != 0) flags |= CREAT;
|
||||
if (c_flags & bun.O.EXCL != 0) flags |= EXCL;
|
||||
if (c_flags & bun.O.TRUNC != 0) flags |= TRUNC;
|
||||
if (c_flags & bun.O.APPEND != 0) flags |= APPEND;
|
||||
if (c_flags & bun.O.EXCL != 0) flags |= EXCL;
|
||||
if (c_flags & bun.O.NONBLOCK != 0) flags |= NONBLOCK;
|
||||
// SYNC and DSYNC must be mutually exclusive for libuv on Windows.
|
||||
// On Linux, bun.O.SYNC (0o4010000) is a superset of bun.O.DSYNC
|
||||
// (0o10000), so checking SYNC first ensures we emit only UV_FS_O_SYNC
|
||||
// when both bits are present. libuv's fs__open rejects having both set.
|
||||
if (c_flags & bun.O.SYNC != 0) {
|
||||
flags |= SYNC;
|
||||
} else if (c_flags & bun.O.DSYNC != 0) {
|
||||
flags |= DSYNC;
|
||||
}
|
||||
if (c_flags & bun.O.NOFOLLOW != 0) flags |= NOFOLLOW;
|
||||
if (c_flags & bun.O.DIRECT != 0) flags |= DIRECT;
|
||||
if (c_flags & FILEMAP != 0) flags |= FILEMAP;
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
/// Convert from libuv/Windows MSVC O_ flags to internal bun.O flags.
|
||||
/// This is the inverse of fromBunO and is needed because fs.constants
|
||||
/// exposes the platform's native C values to JavaScript, but internally
|
||||
/// Bun normalizes all flags to the bun.O (POSIX-like) representation.
|
||||
///
|
||||
/// Only maps flags that have non-zero libuv values on Windows.
|
||||
/// NOFOLLOW, NONBLOCK, DIRECTORY, NOATIME, NOCTTY, SYMLINK are all 0
|
||||
/// in libuv on Windows (no-ops) and cannot be recovered from a bitmask.
|
||||
/// When adding new flag mappings, keep in sync with fromBunO.
|
||||
pub fn toBunO(uv_flags: i32) i32 {
|
||||
var flags: i32 = 0;
|
||||
|
||||
if (uv_flags & WRONLY != 0) flags |= bun.O.WRONLY;
|
||||
if (uv_flags & RDWR != 0) flags |= bun.O.RDWR;
|
||||
if (uv_flags & CREAT != 0) flags |= bun.O.CREAT;
|
||||
if (uv_flags & EXCL != 0) flags |= bun.O.EXCL;
|
||||
if (uv_flags & TRUNC != 0) flags |= bun.O.TRUNC;
|
||||
if (uv_flags & APPEND != 0) flags |= bun.O.APPEND;
|
||||
// SYNC takes priority over DSYNC (see fromBunO comment).
|
||||
if (uv_flags & SYNC != 0) {
|
||||
flags |= bun.O.SYNC;
|
||||
} else if (uv_flags & DSYNC != 0) {
|
||||
flags |= bun.O.DSYNC;
|
||||
}
|
||||
if (uv_flags & DIRECT != 0) flags |= bun.O.DIRECT;
|
||||
if (uv_flags & FILEMAP != 0) flags |= FILEMAP;
|
||||
|
||||
return flags;
|
||||
}
|
||||
};
|
||||
|
||||
const _O_WRONLY = 0x0001;
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
isIntelMacOS,
|
||||
isPosix,
|
||||
isWindows,
|
||||
tempDir,
|
||||
tempDirWithFiles,
|
||||
tmpdirSync,
|
||||
} from "harness";
|
||||
@@ -3680,3 +3681,151 @@ it("overflowing mode doesn't crash", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
describe("numeric flags produce same result as string flags", () => {
|
||||
it("numeric O_CREAT|O_TRUNC|O_WRONLY is equivalent to 'w'", () => {
|
||||
const { O_CREAT, O_TRUNC, O_WRONLY } = constants;
|
||||
const numericFlag = O_CREAT | O_TRUNC | O_WRONLY;
|
||||
|
||||
using dir = tempDir("numeric-flags", {});
|
||||
const fileStr = join(String(dir), "string.txt");
|
||||
const fileNum = join(String(dir), "numeric.txt");
|
||||
|
||||
const fd1 = openSync(fileStr, "w", 0o666);
|
||||
writeSync(fd1, "hello");
|
||||
closeSync(fd1);
|
||||
|
||||
const fd2 = openSync(fileNum, numericFlag, 0o666);
|
||||
writeSync(fd2, "hello");
|
||||
closeSync(fd2);
|
||||
|
||||
expect(readFileSync(fileNum, "utf8")).toBe(readFileSync(fileStr, "utf8"));
|
||||
});
|
||||
|
||||
it("numeric O_CREAT|O_WRONLY|O_APPEND is equivalent to 'a'", () => {
|
||||
const { O_APPEND, O_CREAT, O_WRONLY } = constants;
|
||||
const numericFlag = O_CREAT | O_WRONLY | O_APPEND;
|
||||
|
||||
using dir = tempDir("numeric-flags", {});
|
||||
const fileStr = join(String(dir), "string.txt");
|
||||
const fileNum = join(String(dir), "numeric.txt");
|
||||
|
||||
const fd1 = openSync(fileStr, "a", 0o666);
|
||||
writeSync(fd1, "first");
|
||||
closeSync(fd1);
|
||||
const fd1b = openSync(fileStr, "a", 0o666);
|
||||
writeSync(fd1b, "second");
|
||||
closeSync(fd1b);
|
||||
|
||||
const fd2 = openSync(fileNum, numericFlag, 0o666);
|
||||
writeSync(fd2, "first");
|
||||
closeSync(fd2);
|
||||
const fd2b = openSync(fileNum, numericFlag, 0o666);
|
||||
writeSync(fd2b, "second");
|
||||
closeSync(fd2b);
|
||||
|
||||
expect(readFileSync(fileNum, "utf8")).toBe(readFileSync(fileStr, "utf8"));
|
||||
expect(readFileSync(fileNum, "utf8")).toBe("firstsecond");
|
||||
});
|
||||
|
||||
it("numeric O_CREAT|O_RDWR|O_TRUNC is equivalent to 'w+'", () => {
|
||||
const { O_CREAT, O_RDWR, O_TRUNC } = constants;
|
||||
const numericFlag = O_CREAT | O_RDWR | O_TRUNC;
|
||||
|
||||
using dir = tempDir("numeric-flags", {});
|
||||
const file = join(String(dir), "readwrite.txt");
|
||||
|
||||
const fd = openSync(file, numericFlag, 0o666);
|
||||
writeSync(fd, "read-write");
|
||||
|
||||
// Read back from the same fd to verify O_RDWR actually grants read access.
|
||||
const buf = Buffer.alloc(10);
|
||||
const bytesRead = readSync(fd, buf, 0, 10, 0);
|
||||
closeSync(fd);
|
||||
|
||||
expect(buf.toString("utf8", 0, bytesRead)).toBe("read-write");
|
||||
});
|
||||
|
||||
it("numeric O_RDONLY reads existing file", () => {
|
||||
const { O_RDONLY } = constants;
|
||||
using dir = tempDir("numeric-flags", {});
|
||||
const file = join(String(dir), "readonly.txt");
|
||||
|
||||
writeFileSync(file, "existing content");
|
||||
|
||||
const fd = openSync(file, O_RDONLY);
|
||||
const buf = Buffer.alloc(50);
|
||||
const bytesRead = readSync(fd, buf);
|
||||
closeSync(fd);
|
||||
|
||||
expect(buf.slice(0, bytesRead).toString("utf8")).toBe("existing content");
|
||||
});
|
||||
|
||||
it("numeric O_CREAT|O_EXCL|O_RDWR fails on existing file", () => {
|
||||
const { O_CREAT, O_EXCL, O_RDWR } = constants;
|
||||
const numericFlag = O_CREAT | O_EXCL | O_RDWR;
|
||||
|
||||
using dir = tempDir("numeric-flags", {});
|
||||
const file = join(String(dir), "excl.txt");
|
||||
|
||||
// First open should succeed (creates the file).
|
||||
const fd = openSync(file, numericFlag, 0o666);
|
||||
closeSync(fd);
|
||||
|
||||
// Second open with O_EXCL should fail (file already exists).
|
||||
expect(() => openSync(file, numericFlag, 0o666)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
describe("synchronous I/O string flags", () => {
|
||||
it("'rs' opens existing file for reading", () => {
|
||||
using dir = tempDir("sync-flags", {
|
||||
"existing.txt": "sync content",
|
||||
});
|
||||
|
||||
const fd = openSync(join(String(dir), "existing.txt"), "rs");
|
||||
const buf = Buffer.alloc(20);
|
||||
const bytesRead = readSync(fd, buf);
|
||||
closeSync(fd);
|
||||
|
||||
expect(buf.slice(0, bytesRead).toString("utf8")).toBe("sync content");
|
||||
});
|
||||
|
||||
it("'rs+' opens existing file for read-write", () => {
|
||||
using dir = tempDir("sync-flags", {
|
||||
"existing.txt": "original",
|
||||
});
|
||||
const file = join(String(dir), "existing.txt");
|
||||
|
||||
const fd = openSync(file, "rs+");
|
||||
writeSync(fd, "replaced");
|
||||
closeSync(fd);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("replaced");
|
||||
});
|
||||
|
||||
it("'as' creates and appends to file", () => {
|
||||
using dir = tempDir("sync-flags", {});
|
||||
const file = join(String(dir), "appended.txt");
|
||||
|
||||
const fd = openSync(file, "as");
|
||||
writeSync(fd, "sync-append");
|
||||
closeSync(fd);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("sync-append");
|
||||
});
|
||||
|
||||
it("'as+' creates and appends with read access", () => {
|
||||
using dir = tempDir("sync-flags", {});
|
||||
const file = join(String(dir), "appended-rw.txt");
|
||||
|
||||
const fd = openSync(file, "as+");
|
||||
writeSync(fd, "hello");
|
||||
|
||||
const buf = Buffer.alloc(10);
|
||||
const bytesRead = readSync(fd, buf, 0, 10, 0);
|
||||
closeSync(fd);
|
||||
|
||||
expect(buf.toString("utf8", 0, bytesRead)).toBe("hello");
|
||||
});
|
||||
});
|
||||
|
||||
88
test/regression/issue/26740.test.ts
Normal file
88
test/regression/issue/26740.test.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { expect, test } from "bun:test";
|
||||
|
||||
// Regression test for https://github.com/oven-sh/bun/issues/26740
|
||||
// FormData multipart parsing was truncating binary file content at null bytes
|
||||
// for files 8 bytes or smaller due to Semver.String inline storage optimization.
|
||||
|
||||
test("FormData preserves binary data with null bytes in small files", async () => {
|
||||
const testCases = [
|
||||
{ name: "8 bytes with null at position 3", data: [0x01, 0x02, 0x03, 0x00, 0x05, 0x06, 0x07, 0x08] },
|
||||
{ name: "4 bytes with null at end", data: [0x1f, 0x8b, 0x08, 0x00] },
|
||||
{ name: "1 byte null", data: [0x00] },
|
||||
{ name: "all nulls (8 bytes)", data: [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] },
|
||||
{ name: "7 bytes ending with null", data: [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00] },
|
||||
{ name: "6 bytes starting with null", data: [0x00, 0x02, 0x03, 0x04, 0x05, 0x06] },
|
||||
];
|
||||
|
||||
await using server = Bun.serve({
|
||||
port: 0,
|
||||
async fetch(req) {
|
||||
const formData = await req.formData();
|
||||
const file = formData.get("file") as File;
|
||||
const bytes = new Uint8Array(await file.arrayBuffer());
|
||||
return Response.json({
|
||||
expectedSize: parseInt(req.headers.get("x-expected-size") || "0"),
|
||||
actualSize: bytes.byteLength,
|
||||
content: Array.from(bytes),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
for (const tc of testCases) {
|
||||
const content = new Uint8Array(tc.data);
|
||||
const file = new File([content], "test.bin", { type: "application/octet-stream" });
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const res = await fetch(`http://localhost:${server.port}`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: { "x-expected-size": String(tc.data.length) },
|
||||
});
|
||||
|
||||
const result = (await res.json()) as { expectedSize: number; actualSize: number; content: number[] };
|
||||
|
||||
expect(result.actualSize).toBe(result.expectedSize);
|
||||
expect(result.content).toEqual(tc.data);
|
||||
}
|
||||
});
|
||||
|
||||
test("FormData preserves binary data in larger files (> 8 bytes)", async () => {
|
||||
// This should have worked before the fix, but let's verify it still works
|
||||
const testCases = [
|
||||
{ name: "16 bytes with nulls", data: Array.from({ length: 16 }, (_, i) => (i % 3 === 0 ? 0x00 : i)) },
|
||||
{ name: "9 bytes with null at start", data: [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08] },
|
||||
];
|
||||
|
||||
await using server = Bun.serve({
|
||||
port: 0,
|
||||
async fetch(req) {
|
||||
const formData = await req.formData();
|
||||
const file = formData.get("file") as File;
|
||||
const bytes = new Uint8Array(await file.arrayBuffer());
|
||||
return Response.json({
|
||||
expectedSize: parseInt(req.headers.get("x-expected-size") || "0"),
|
||||
actualSize: bytes.byteLength,
|
||||
content: Array.from(bytes),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
for (const tc of testCases) {
|
||||
const content = new Uint8Array(tc.data);
|
||||
const file = new File([content], "test.bin", { type: "application/octet-stream" });
|
||||
const formData = new FormData();
|
||||
formData.append("file", file);
|
||||
|
||||
const res = await fetch(`http://localhost:${server.port}`, {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
headers: { "x-expected-size": String(tc.data.length) },
|
||||
});
|
||||
|
||||
const result = (await res.json()) as { expectedSize: number; actualSize: number; content: number[] };
|
||||
|
||||
expect(result.actualSize).toBe(result.expectedSize);
|
||||
expect(result.content).toEqual(tc.data);
|
||||
}
|
||||
});
|
||||
115
test/regression/issue/27974.test.ts
Normal file
115
test/regression/issue/27974.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { isWindows, tempDir } from "harness";
|
||||
import { closeSync, constants, open as openCb, openSync, readFileSync, writeSync } from "node:fs";
|
||||
import { open } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
test("fs.openSync with numeric O_CREAT | O_TRUNC | O_WRONLY flags", () => {
|
||||
const { O_CREAT, O_TRUNC, O_WRONLY } = constants;
|
||||
const flag = O_TRUNC | O_CREAT | O_WRONLY;
|
||||
|
||||
using dir = tempDir("issue-27974", {});
|
||||
const file = join(String(dir), "test.txt");
|
||||
|
||||
const fd = openSync(file, flag, 0o666);
|
||||
writeSync(fd, "hello world");
|
||||
closeSync(fd);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("hello world");
|
||||
});
|
||||
|
||||
test("fs.openSync with numeric O_CREAT | O_WRONLY flags (no O_TRUNC)", () => {
|
||||
const { O_CREAT, O_WRONLY } = constants;
|
||||
const flag = O_CREAT | O_WRONLY;
|
||||
|
||||
using dir = tempDir("issue-27974", {});
|
||||
const file = join(String(dir), "test2.txt");
|
||||
|
||||
const fd = openSync(file, flag, 0o666);
|
||||
writeSync(fd, "created");
|
||||
closeSync(fd);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("created");
|
||||
});
|
||||
|
||||
test.if(isWindows)("fs.openSync with UV_FS_O_FILEMAP | O_CREAT | O_TRUNC | O_WRONLY", () => {
|
||||
const { O_CREAT, O_TRUNC, O_WRONLY, UV_FS_O_FILEMAP } = constants;
|
||||
const flag = UV_FS_O_FILEMAP | O_TRUNC | O_CREAT | O_WRONLY;
|
||||
|
||||
using dir = tempDir("issue-27974", {});
|
||||
const file = join(String(dir), "filemap.txt");
|
||||
|
||||
const fd = openSync(file, flag, 0o666);
|
||||
writeSync(fd, "filemap content");
|
||||
closeSync(fd);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("filemap content");
|
||||
});
|
||||
|
||||
test("fs.openSync with numeric O_RDWR | O_CREAT | O_EXCL flags", () => {
|
||||
const { O_CREAT, O_RDWR, O_EXCL } = constants;
|
||||
const flag = O_CREAT | O_RDWR | O_EXCL;
|
||||
|
||||
using dir = tempDir("issue-27974", {});
|
||||
const file = join(String(dir), "exclusive.txt");
|
||||
|
||||
const fd = openSync(file, flag, 0o666);
|
||||
writeSync(fd, "exclusive");
|
||||
closeSync(fd);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("exclusive");
|
||||
|
||||
// Opening again with O_EXCL should fail since file exists.
|
||||
expect(() => openSync(file, flag, 0o666)).toThrow();
|
||||
});
|
||||
|
||||
test("fs.promises.open with numeric O_CREAT | O_TRUNC | O_WRONLY flags", async () => {
|
||||
const { O_CREAT, O_TRUNC, O_WRONLY } = constants;
|
||||
const flag = O_TRUNC | O_CREAT | O_WRONLY;
|
||||
|
||||
using dir = tempDir("issue-27974", {});
|
||||
const file = join(String(dir), "async.txt");
|
||||
|
||||
await using fh = await open(file, flag, 0o666);
|
||||
await fh.write("async hello");
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("async hello");
|
||||
});
|
||||
|
||||
test("fs.open (callback) with numeric O_CREAT | O_TRUNC | O_WRONLY flags", async () => {
|
||||
const { O_CREAT, O_TRUNC, O_WRONLY } = constants;
|
||||
const flag = O_TRUNC | O_CREAT | O_WRONLY;
|
||||
|
||||
using dir = tempDir("issue-27974", {});
|
||||
const file = join(String(dir), "callback.txt");
|
||||
|
||||
const { promise, resolve, reject } = Promise.withResolvers<number>();
|
||||
openCb(file, flag, 0o666, (err, fd) => {
|
||||
if (err) reject(err);
|
||||
else resolve(fd);
|
||||
});
|
||||
|
||||
const fd = await promise;
|
||||
writeSync(fd, "callback hello");
|
||||
closeSync(fd);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("callback hello");
|
||||
});
|
||||
|
||||
test("fs.openSync with numeric O_APPEND | O_CREAT | O_WRONLY flags", () => {
|
||||
const { O_APPEND, O_CREAT, O_WRONLY } = constants;
|
||||
const flag = O_APPEND | O_CREAT | O_WRONLY;
|
||||
|
||||
using dir = tempDir("issue-27974", {});
|
||||
const file = join(String(dir), "append.txt");
|
||||
|
||||
const fd1 = openSync(file, flag, 0o666);
|
||||
writeSync(fd1, "first");
|
||||
closeSync(fd1);
|
||||
|
||||
const fd2 = openSync(file, flag, 0o666);
|
||||
writeSync(fd2, "second");
|
||||
closeSync(fd2);
|
||||
|
||||
expect(readFileSync(file, "utf8")).toBe("firstsecond");
|
||||
});
|
||||
Reference in New Issue
Block a user