Skip to content

Commit

Permalink
feat(windows): allow open and Bun.file() with /dev/null (oven-sh#8499)
Browse files Browse the repository at this point in the history
* DEV NULL

* oops

* ok
  • Loading branch information
paperclover authored and sroussey committed Feb 2, 2024
1 parent 6cd20b6 commit bd14cc7
Show file tree
Hide file tree
Showing 7 changed files with 47 additions and 44 deletions.
6 changes: 5 additions & 1 deletion src/bun.js/node/node_fs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4486,7 +4486,11 @@ pub const NodeFS = struct {
}

pub fn open(this: *NodeFS, args: Arguments.Open, comptime _: Flavor) Maybe(Return.Open) {
const path = args.path.sliceZ(&this.sync_error_buf);
const path = if (Environment.isWindows and bun.strings.eqlComptime(args.path.slice(), "/dev/null"))
"\\\\.\\NUL"
else
args.path.sliceZ(&this.sync_error_buf);

return switch (Syscall.open(path, @intFromEnum(args.flags), args.mode)) {
.err => |err| .{
.err = err.withPath(args.path.slice()),
Expand Down
28 changes: 17 additions & 11 deletions src/bun.js/webcore/blob.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1519,35 +1519,41 @@ pub const Blob = struct {
return ptr.toJS(globalObject);
}

pub fn findOrCreateFileFromPath(path_: *JSC.Node.PathOrFileDescriptor, globalThis: *JSGlobalObject) Blob {
pub fn findOrCreateFileFromPath(path_or_fd: *JSC.Node.PathOrFileDescriptor, globalThis: *JSGlobalObject) Blob {
var vm = globalThis.bunVM();
const allocator = bun.default_allocator;

const path: JSC.Node.PathOrFileDescriptor = brk: {
switch (path_.*) {
switch (path_or_fd.*) {
.path => {
const slice = path_.path.slice();
const slice = path_or_fd.path.slice();

if (Environment.isWindows and bun.strings.eqlComptime(slice, "/dev/null")) {
// it is okay to use rodata here, because the '.string' case
// in PathLike.deinit does not free anything.
path_or_fd.* = .{ .path = .{ .string = bun.PathString.init("\\\\.\\NUL") } };
}

if (vm.standalone_module_graph) |graph| {
if (graph.find(slice)) |file| {
defer {
if (path_.path != .string) {
path_.deinit();
path_.* = .{ .path = .{ .string = bun.PathString.empty } };
if (path_or_fd.path != .string) {
path_or_fd.deinit();
path_or_fd.* = .{ .path = .{ .string = bun.PathString.empty } };
}
}

return file.blob(globalThis).dupe();
}
}

path_.toThreadSafe();
const copy = path_.*;
path_.* = .{ .path = .{ .string = bun.PathString.empty } };
path_or_fd.toThreadSafe();
const copy = path_or_fd.*;
path_or_fd.* = .{ .path = .{ .string = bun.PathString.empty } };
break :brk copy;
},
.fd => {
switch (bun.FDTag.get(path_.fd)) {
switch (bun.FDTag.get(path_or_fd.fd)) {
.stdin => return Blob.initWithStore(
vm.rareData().stdin(),
globalThis,
Expand All @@ -1562,7 +1568,7 @@ pub const Blob = struct {
),
else => {},
}
break :brk path_.*;
break :brk path_or_fd.*;
},
}
};
Expand Down
24 changes: 0 additions & 24 deletions test/js/bun/plugin/muh.ts

This file was deleted.

6 changes: 3 additions & 3 deletions test/js/bun/shell/leak.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { $ } from "bun";
import { describe, expect, test } from "bun:test";
import { bunEnv } from "harness";
import { appendFileSync, closeSync, openSync, writeFileSync } from "node:fs";
import { tmpdir } from "os";
import { tmpdir, devNull } from "os";
import { join } from "path";
import { TestBuilder } from "./util";

Expand Down Expand Up @@ -55,13 +55,13 @@ describe("fd leak", () => {
await builder().quiet().run();
}

const baseline = openSync("/dev/null", "r");
const baseline = openSync(devNull, "r");
closeSync(baseline);

for (let i = 0; i < runs; i++) {
await builder().quiet().run();
}
const fd = openSync("/dev/null", "r");
const fd = openSync(devNull, "r");
closeSync(fd);
expect(fd).toBe(baseline);
}, 100_000);
Expand Down
6 changes: 3 additions & 3 deletions test/js/bun/spawn/spawn-streaming-stdin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import { it, test, expect } from "bun:test";
import { spawn } from "bun";
import { bunExe, bunEnv, gcTick } from "harness";
import { closeSync, openSync } from "fs";
import { tmpdir } from "node:os";
import { tmpdir, devNull } from "node:os";
import { join } from "path";
import { unlinkSync } from "node:fs";

const N = 100;
test("spawn can write to stdin multiple chunks", async () => {
const maxFD = openSync("/dev/null", "w");
const maxFD = openSync(devNull, "w");
for (let i = 0; i < N; i++) {
var exited;
await (async function () {
Expand Down Expand Up @@ -59,7 +59,7 @@ test("spawn can write to stdin multiple chunks", async () => {
}

closeSync(maxFD);
const newMaxFD = openSync("/dev/null", "w");
const newMaxFD = openSync(devNull, "w");
closeSync(newMaxFD);

// assert we didn't leak any file descriptors
Expand Down
5 changes: 3 additions & 2 deletions test/js/bun/spawn/spawn-streaming-stdout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { it, test, expect } from "bun:test";
import { spawn } from "bun";
import { bunExe, bunEnv, gcTick } from "harness";
import { closeSync, openSync } from "fs";
import { devNull } from "os";

test("spawn can read from stdout multiple chunks", async () => {
gcTick(true);
Expand Down Expand Up @@ -35,11 +36,11 @@ test("spawn can read from stdout multiple chunks", async () => {
await proc.exited;
})();
if (maxFD === -1) {
maxFD = openSync("/dev/null", "w");
maxFD = openSync(devNull, "w");
closeSync(maxFD);
}
}
const newMaxFD = openSync("/dev/null", "w");
const newMaxFD = openSync(devNull, "w");
closeSync(newMaxFD);
expect(newMaxFD).toBe(maxFD);
}, 60_000);
16 changes: 16 additions & 0 deletions test/js/bun/util/bun-file-windows.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { openSync, closeSync } from "fs";
import { open } from "fs/promises";

test('Bun.file("/dev/null") works on windows', async () => {
expect(await Bun.file("/dev/null").arrayBuffer()).toHaveLength(0);
});

test('openSync("/dev/null") works on windows', async () => {
const handle = openSync("/dev/null", "r");
closeSync(handle);
});

test('open("/dev/null") works on windows', async () => {
const handle = await open("/dev/null", "r");
await handle.close();
});

0 comments on commit bd14cc7

Please sign in to comment.