Files
bun.sh/test/regression/issue/23292.test.ts
robobun 44f2328111 fix(fs.access): handle Windows device paths correctly (#25023)
## Summary

Fixes #23292

`fs.access()` and `fs.accessSync()` threw EUNKNOWN (-134) when checking
named pipes on Windows (paths like `\.\pipe\name`), but Node.js worked
fine.

**Repro:**
```ts
// Server creates pipe at \.\pipe\bun-test
import net from 'net';
const server = net.createServer();
server.listen('\\.\pipe\bun-test');

// Client tries to check if pipe exists
import fs from 'fs';
fs.accessSync('\\.\pipe\bun-test', fs.constants.F_OK);
// Error: EUNKNOWN: unknown error, access '\.\pipe\bun-test'
```

## Root Cause

The `osPathKernel32` function normalizes paths before passing to Windows
APIs. The normalization logic treats a single `.` as a "current
directory" component and removes it, so `\.\pipe\name` incorrectly
became `\pipe\name` - an invalid path.

## Solution

Detect Windows device paths (starting with `\.\` or `\?\`) and skip
normalization for these special paths, preserving the device prefix.

## Test Plan

- [x] Added regression test `test/regression/issue/23292.test.ts`
- [x] Test fails with system bun (v1.3.3): 3 failures (EUNKNOWN)
- [x] Test passes with fix: 4 pass

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <claude-bot@bun.sh>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2025-11-26 00:07:51 -08:00

73 lines
1.9 KiB
TypeScript

// https://github.com/oven-sh/bun/issues/23292
// fs.access() and fs.accessSync() should work with Windows named pipes
import { expect, test } from "bun:test";
import { isWindows } from "harness";
import { randomUUID } from "node:crypto";
import { once } from "node:events";
import fs from "node:fs";
import net from "node:net";
test.if(isWindows)("fs.accessSync should work with named pipes", async () => {
const pipeName = `\\\\.\\pipe\\bun-test-${randomUUID()}`;
const server = net.createServer();
server.listen(pipeName);
await once(server, "listening");
try {
// Should not throw - the pipe exists
fs.accessSync(pipeName, fs.constants.F_OK);
// Test with R_OK as well
fs.accessSync(pipeName, fs.constants.R_OK);
} finally {
server.close();
}
});
test.if(isWindows)("fs.access should work with named pipes", async () => {
const pipeName = `\\\\.\\pipe\\bun-test-${randomUUID()}`;
const server = net.createServer();
server.listen(pipeName);
await once(server, "listening");
try {
// Test fs.access with callback
const { promise, resolve, reject } = Promise.withResolvers<void>();
fs.access(pipeName, fs.constants.F_OK, err => {
if (err) {
reject(err);
} else {
resolve();
}
});
await promise;
} finally {
server.close();
}
});
test.if(isWindows)("fs.promises.access should work with named pipes", async () => {
const pipeName = `\\\\.\\pipe\\bun-test-${randomUUID()}`;
const server = net.createServer();
server.listen(pipeName);
await once(server, "listening");
try {
// Should not throw - the pipe exists
await fs.promises.access(pipeName, fs.constants.F_OK);
} finally {
server.close();
}
});
test.if(isWindows)("fs.accessSync should throw ENOENT for non-existent named pipe", () => {
const pipeName = `\\\\.\\pipe\\bun-test-nonexistent-${randomUUID()}`;
expect(() => {
fs.accessSync(pipeName, fs.constants.F_OK);
}).toThrow();
});