diff --git a/src/relay/relay-primary-channel.test.ts b/src/relay/relay-primary-channel.test.ts new file mode 100644 index 00000000000..d1ac209e161 --- /dev/null +++ b/src/relay/relay-primary-channel.test.ts @@ -0,0 +1,29 @@ +import { win32 } from 'node:path' +import { describe, expect, it } from 'vitest' +import { nullDevicePath } from './relay-primary-channel' + +describe('nullDevicePath', () => { + it('names the POSIX null device off win32', () => { + expect(nullDevicePath('linux')).toBe('/dev/null') + expect(nullDevicePath('darwin')).toBe('/dev/null') + }) + + /** + * The defect this pins: `openSync('NUL')` on Windows does NOT open the null device. + * node runs the path through `toNamespacedPath`, which resolves it against cwd and + * prefixes `\\?\` — and `\\?\` turns off DOS device-name mapping, so CreateFileW makes + * a real file. v1.4.203's Windows installer shipped one at + * `resources/relay/win32-x64/NUL` because of it. + */ + it('uses a device path win32 cannot rewrite into a file in the relay cwd', () => { + const path = nullDevicePath('win32') + + expect(path).toBe('\\\\.\\NUL') + expect(win32.toNamespacedPath(path)).toBe(path) + // Bare `NUL` never survives as a device name: it is resolved against cwd, and a + // drive-letter cwd then also takes the `\\?\` prefix. Spelled absolute because off + // Windows `resolve` finds no drive letter and stops before that second rewrite. + expect(win32.toNamespacedPath('NUL')).not.toBe('NUL') + expect(win32.toNamespacedPath(String.raw`C:\relay\NUL`)).toBe(String.raw`\\?\C:\relay\NUL`) + }) +}) diff --git a/src/relay/relay-primary-channel.ts b/src/relay/relay-primary-channel.ts index 9b2e50eaaa1..e3dbffeae7f 100644 --- a/src/relay/relay-primary-channel.ts +++ b/src/relay/relay-primary-channel.ts @@ -2,6 +2,17 @@ import { closeSync, openSync } from 'node:fs' import { RelayDispatcher } from './dispatcher' import { RELAY_SENTINEL } from './protocol' +/** + * Why the `\\.\` device prefix and not bare `NUL`: node's fs resolves a relative path + * through `toNamespacedPath`, which hands CreateFileW a `\\?\C:\…\NUL` — and that prefix + * disables DOS device-name mapping, so the open creates a real FILE named `NUL` in the + * relay's cwd and pins fds 0/1 to it. One shipped in the 1.4.203 Windows installer as + * `resources/relay/win32-x64/NUL`. A `\\.\` path is passed through verbatim. + */ +export function nullDevicePath(platform: NodeJS.Platform = process.platform): string { + return platform === 'win32' ? String.raw`\\.\NUL` : '/dev/null' +} + export class RelayPrimaryChannel { readonly dispatcher: RelayDispatcher private stdoutAlive = true @@ -111,14 +122,13 @@ export class RelayPrimaryChannel { // Already closed by the peer. } } - const devNull = process.platform === 'win32' ? 'NUL' : '/dev/null' try { - openSync(devNull, 'r') + openSync(nullDevicePath(), 'r') } catch { // Best-effort pin of the lowest free descriptor. } try { - openSync(devNull, 'w') + openSync(nullDevicePath(), 'w') } catch { // Best-effort pin of the next free descriptor. }