Skip to content

Implement Windows host support (WHPX) - #90

Open
mrexodia wants to merge 14 commits into
earendil-works:mainfrom
mrexodia:windows-support
Open

Implement Windows host support (WHPX)#90
mrexodia wants to merge 14 commits into
earendil-works:mainfrom
mrexodia:windows-support

Conversation

@mrexodia

@mrexodia mrexodia commented Apr 15, 2026

Copy link
Copy Markdown

Closes #21

Session: https://pi.dev/session/#009bb13df5408d6e7dff80631eb6df69
Review: https://pi.dev/session/#79f60c5f2455ebc93d66e3a31b0febbd

Manual testing report (this is what I ran myself to confirm things work): https://gist.github.com/mrexodia/b3261b1a5d05b37a7834d36f997ded74

Currently working on getting the GitHub Actions to actually pass (also tests Windows), might take a little bit...

Happy to do some more work to clean this up into separate commits if you are willing to merge this in principle. I glanced over the code and most things looked reasonable, but I only focused on getting it to actually work for now.

Contribution Agreement

Please ensure this PR follows the guidelines in CONTRIBUTING.md. (NOTE: this file does not exist in the repo)

By submitting this pull request, I confirm the following:

I understand that the entity Earendil Inc. (incorporated in the state of Delaware in 2025) needs some rights from me in order to utilize my contributions in this PR. As a contributor I retain all rights, title and interest in and to my contributions, and by keeping this boilerplate intact I confirm that Earendil Inc. can use, modify, copy, and redistribute my contributions, under Earendil Inc.'s choice of terms.

@mrexodia
mrexodia force-pushed the windows-support branch 2 times, most recently from ffd5e6e to eecacb6 Compare April 16, 2026 00:02
@mrexodia

Copy link
Copy Markdown
Author

@codex

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e4ec901c72

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread host/src/sandbox/server-transport.ts Outdated
Comment thread host/src/cli/mount-spec.ts Outdated
@mrexodia
mrexodia force-pushed the windows-support branch 2 times, most recently from da9924d to a5d828a Compare April 16, 2026 21:25
@mrexodia
mrexodia marked this pull request as ready for review April 16, 2026 22:12

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5d828aa3a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread host/src/sandbox/server-ops.ts
@mrexodia
mrexodia force-pushed the windows-support branch 2 times, most recently from 9dd34a8 to 16a68d3 Compare April 22, 2026 20:49
@cravenceiling

Copy link
Copy Markdown

+1

@mrexodia

Copy link
Copy Markdown
Author

I rebased, workflows are currently running on my repo: https://github.com/mrexodia/gondolin/actions/runs/26248033650

@mrexodia

Copy link
Copy Markdown
Author

https://github.com/mrexodia/gondolin/actions/runs/26251373997 CI is green again!

mrexodia added 12 commits July 10, 2026 17:17
secret-host-suggestions.ts hardcoded a POSIX /tmp fallback that was
never updated when the rest of the codebase moved to os.tmpdir() for
Windows support, so `--host-secret NAME` (without an explicit @host)
threw ENOENT on Windows before the VM even started.
quoteCmdArg caret-escaped characters (including embedded double
quotes) inside a quoted argument, which corrupts cmd.exe's own
command-line parsing rather than producing a literal character -
verified empirically against a real cmd.exe that this silently broke
the whole invocation. Also drops the unnecessary `call` prefix when
re-exec'ing through cmd.exe: `call`'s own argument re-parsing doesn't
respect caret-escaped metacharacters the way a plain `/s`-wrapped
command line does, which is what actually made metacharacter escaping
(&, |, <, >) work correctly. Arguments containing a literal double
quote are now rejected outright rather than silently mis-invoked,
since there's no reliable way to deliver one to a .bat/.cmd file's
argv through cmd.exe.

Adds unit tests plus a real cmd.exe end-to-end test (skipped off
Windows) since this logic previously had no coverage at all.
host/src/qemu/img.ts and host/src/sandbox/server-options.ts each
independently reimplemented near-identical Windows QEMU-family binary
discovery (probe via --version, search ProgramW6432/ProgramFiles
install roots, dedupe, pick first responder), differing only in the
target binary basename - a fix to the install-path heuristics had to
be applied in both places or they'd silently drift apart. Extracts
the shared logic into qemu/locate-binary.ts and rewires both call
sites, plus the third near-duplicate in the test WHPX probe helper.
…ints

Standalone addition so later commits (and a later dedup pass) have a
single place to construct a throwaway win32-vs-unix test endpoint,
instead of every test file hand-rolling the same platform branch.
selectRngObject() returned null on win32, so every Windows-hosted
guest booted with no virtio-rng device at all - a real capability
regression vs Linux/macOS hosts, previously undocumented and
untested. Uses QEMU's cross-platform rng-builtin backend instead
(host getrandom()/BCryptGenRandom, no /dev/urandom-equivalent device
file needed, available since QEMU 5.0).
qemuCanInitializeAccel probes WHPX support via synchronous
spawnSync with up to a 1.5s timeout, invoked from selectAccel on
every sandbox/VM creation. spawnSync blocks the entire Node event
loop (not just the calling async context), so the first WHPX probe
for a given qemuPath stalls any other in-flight sandbox's I/O in the
same host process.

Adds an async primeAccelProbeCache that performs the same probe via
non-blocking spawn and populates the same cache selectAccel reads
from. resolveSandboxServerOptionsAsync (the path SandboxServer.create
uses, i.e. the recommended API) awaits it before the synchronous accel
selection runs, so the common case never blocks. The synchronous
SandboxServer constructor has no async alternative available and
keeps today's existing (unworsened) blocking behavior.
The QMP idle-pause monitor was hardcoded to a Unix domain socket
(defaultQmpSocketPath, executeQmpCommand, and the buildQemuArgs
-qmp arg), unlike every other virtio/network socket in this
codebase which already went through the cross-platform LocalEndpoint
abstraction. Nothing rejected setting sandbox.qemuIdlePauseMs on
Windows, so a user who opted in would hit a broken QMP connection
attempt at runtime with no test ever having exercised that path.

Reserves a loopback TCP port for the QMP endpoint on Windows (QEMU's
-qmp chardev supports tcp:host:port just as well as unix:path) and
resolves it lazily since the port reservation needs an async
bind/close round trip that can't happen in the constructor - start()
awaits it before spawning qemu, guarded by a new `starting` flag so a
concurrent start() call can't slip past the child-process check while
that's in flight. The one await point is skipped entirely when
idle-pause isn't configured, so the common case stays fully
synchronous like before.

Un-skips and rewrites the "idle pause uses a short QMP socket" test to
exercise a real QMP round trip over whichever transport the platform
actually uses, instead of being unconditionally skipped on Windows.
skipIfBackendUnavailable skipped the entire suite on win32 regardless
of backend - including "qemu", the backend this codebase's Windows
support actually uses - citing a "Windows QEMU suite" that doesn't
exist anywhere in the repo. VM boot+exec, rootfs-resize, VFS mount
parity, and network-egress-mediation-via-httpHooks assertions never
ran on Windows for the one backend that's supported there. Now only
krun (genuinely unsupported on Windows) is skipped; qemu falls
through to the existing shouldSkipVmTests() hardware-accel gate like
every other platform.
All ~16 path-traversal/symlink-escape security tests (the guard that
stops a sandboxed guest from escaping a --mount-hostfs mount root via
symlinks) were unconditionally skipped on win32, on the assumption
symlinks always need elevation there - not true under Developer Mode
or an elevated process, and this left the guard completely untested
wherever it's actually capable of running. Replaces the platform
check with a one-time real symlink-creation probe, so the tests run
for real wherever they can.

Running them for real surfaced one genuine, safe platform behavior
difference: Windows' RemoveDirectory (unlike POSIX rmdir) doesn't
refuse to operate on a directory symlink/junction, it removes the
reparse point itself without following it - equally safe (the real
target is never touched) but doesn't throw ENOTDIR. The affected test
now asserts the platform-appropriate outcome instead of assuming
POSIX semantics everywhere.
The sole test for recoverGitBashPathListSpec mocked runCygpath to
return a hardcoded string instead of exercising real `cygpath -u -p`
output, so a real-world cygpath format difference (spaces, drive
letters, the trailing non-path "ro" token) could silently break the
recovery path while the test kept passing.

Replaces the mock's fixture with a value verified against a real
cygpath.exe invocation (the previous fixture used a path nested under
a typical Git-for-Windows install root, which coincidentally collapses
under cygpath's own POSIX root mount - a misleading, environment-
dependent "realistic-looking" value that wasn't actually representative).
Adds a real end-to-end test exercising defaultRunCygpath's actual
execFileSync("cygpath", ...) call, gated on cygpath being available.
…elper

Six test files each hand-rolled their own win32-vs-unix endpoint
constructor with slightly different literal values (fixed vs
ephemeral ports, different path construction), instead of using the
shared makeTestEndpoint helper - a future change to Windows loopback-
endpoint defaults would need to be found and updated in six places,
and missing one would leave that file silently asserting stale
transport semantics.
…less of host OS

Used the native `path` module (path.join/path.delimiter/path.extname),
which follows the actual host OS's conventions rather than Windows'.
deps.platform lets callers simulate win32 path resolution from a
non-Windows host (exactly what the new unit tests do), but on a real
Linux/macOS CI runner path.delimiter is ":" and path.join uses "/" -
so a simulated "PATH=C:\a;C:\tools" never split correctly and the
.cmd-shim-on-PATH test failed for real on Linux CI. Switches to
path.win32 explicitly, since this function's entire purpose is
resolving a Windows PATH/PATHEXT lookup irrespective of what platform
happens to be running the code.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Windows host support

2 participants