-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
What version of Bun is running?
1.3.10+30e609e08
What platform is your computer?
Microsoft Windows NT 10.0.22631.0 x64
What steps can reproduce the bug?
fs.openSync rejects the UV_FS_O_FILEMAP flag on Windows with EINVAL. Node.js supports this flag (it's a libuv constant exposed via fs.constants.UV_FS_O_FILEMAP). Bun exposes the constant but doesn't handle it.
Minimal repro — save as repro_minimal.cjs:
const fs = require("fs");
const path = require("path");
const os = require("os");
const { O_CREAT, O_TRUNC, O_WRONLY, UV_FS_O_FILEMAP } = fs.constants;
const flag = UV_FS_O_FILEMAP | O_TRUNC | O_CREAT | O_WRONLY;
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "repro-"));
const file = path.join(tmp, "test.txt");
try {
const fd = fs.openSync(file, flag, 0o666);
fs.writeSync(fd, "hello world");
fs.closeSync(fd);
console.log("PASS:", fs.readFileSync(file, "utf8"));
} catch (err) {
console.log("FAIL:", err.code, err.message);
}
fs.rmSync(tmp, { recursive: true, force: true });$ node repro_minimal.cjs
PASS: hello world
$ bun repro_minimal.cjs
FAIL: EINVAL EINVAL: invalid argument, open 'C:\Users\...\test.txt'
Real-world impact — save as repro_tar.cjs (requires npm install tar@7):
const tar = require("tar");
const fs = require("fs");
const path = require("path");
const os = require("os");
async function main() {
console.log("runtime:", typeof Bun !== "undefined" ? "bun" : "node");
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "repro-tar-"));
const tgzPath = path.join(tmp, "cowsay.tgz");
const outDir = path.join(tmp, "out");
fs.mkdirSync(outDir);
const resp = await fetch("https://registry.npmjs.org/cowsay/-/cowsay-1.6.0.tgz");
fs.writeFileSync(tgzPath, Buffer.from(await resp.arrayBuffer()));
const warnings = [];
await tar.x({
file: tgzPath,
cwd: outDir,
strip: 1,
onwarn: (code, message) => warnings.push({ code, message: String(message).slice(0, 100) }),
});
const entries = fs.readdirSync(outDir);
console.log("warnings:", warnings.length);
if (warnings.length > 0) console.log("first:", warnings[0].code, warnings[0].message);
console.log("extracted:", entries);
console.log("has package.json:", entries.includes("package.json"));
fs.rmSync(tmp, { recursive: true, force: true });
}
main().catch(console.error);$ node repro_tar.cjs
runtime: node
warnings: 0
extracted: [ 'build', 'cli.js', 'cows', 'index.d.ts', 'index.js', 'lib', 'LICENSE', 'package.json', 'README.md' ]
has package.json: true
$ bun repro_tar.cjs
runtime: bun
warnings: 202
first: TAR_ENTRY_ERROR EINVAL: invalid argument, open '.../out/LICENSE'
extracted: [ "build", "cows", "lib" ]
has package.json: false
What is the expected behavior?
fs.openSync(path, UV_FS_O_FILEMAP | O_TRUNC | O_CREAT | O_WRONLY, 0o666) should succeed on Windows, consistent with Node.js. Bun already exposes fs.constants.UV_FS_O_FILEMAP (value 536870912 / 0x20000000), so it should be handled by the underlying open syscall.
What do you see instead?
EINVAL: invalid argument, open '...' — the flag is rejected.
Additional information
This silently breaks @npmcli/arborist (used by npm install) and any other package that uses tar@7 on Windows under Bun. tar uses UV_FS_O_FILEMAP for all files < 512KB on Windows as a performance optimization (see tar/src/get-write-flag.ts). Since tar's error handler emits a warning rather than throwing, every file write silently fails — only directories are created. The result is that node_modules appears populated but contains no actual files.
Workaround: set process.env.__FAKE_PLATFORM__ = "linux" before tar is imported, which makes it use the plain 'w' flag instead.