feat(windows): add native Windows WHPX hypervisor support#476
feat(windows): add native Windows WHPX hypervisor support#476lilongen wants to merge 10 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
Adds native Windows support to BoxLite by integrating the Windows Hypervisor Platform (WHPX) backend and adapting core runtime, sandboxing, networking, and build/bundling paths to work cross-platform while keeping the same SDK surface.
Changes:
- Introduces Windows-specific VM lifecycle + process control (async
krun_start/wait, job objects, suspended spawn/resume, Windows watchdog/events, Ctrl+C handling). - Extends cross-platform runtime and guest behavior (AF_UNIX on Windows via
uds_windows, virtiofs→9p fallback, ensuring virtual filesystems mounted in guest). - Updates build system/CI/scripts to bundle and validate Windows runtime deps (kernel/initrd/e2fsprogs/gvproxy DLL/import lib) and add Windows CI workflows.
Reviewed changes
Copilot reviewed 82 out of 84 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/test-utils/src/home.rs | Use OS temp dir instead of hardcoding /tmp for tests. |
| src/test-utils/src/config_matrix.rs | Gate a platform-specific test to macOS/Linux. |
| src/test-utils/src/cache.rs | Cross-platform cache tempdir/symlink + Windows locking adjustments. |
| src/test-utils/Cargo.toml | Make libc a Unix-only dependency. |
| src/guest/src/storage/volume.rs | Make SHARED virtiofs mount optional with fallback directory creation. |
| src/guest/src/storage/virtiofs.rs | Implement virtiofs-first with 9p fallback; improve logging/errors. |
| src/guest/src/service/server.rs | Set TCP_NODELAY on accepted TCP connections to reduce latency. |
| src/guest/src/service/guest.rs | Make guest network configuration failures non-fatal. |
| src/guest/src/mounts.rs | Add virtual filesystem mounting + generalized mount-type detection helper. |
| src/guest/src/main.rs | Ensure /proc,/sys,/dev mounted early in guest startup. |
| src/guest/src/container/zygote.rs | Add init-container build IPC types + handlers and tests. |
| src/guest/src/container/start.rs | Route init container creation through zygote (avoid musl clone3 deadlock). |
| src/deps/libkrun-sys/src/lib.rs | Add new WHPX-related libkrun FFI functions; gate uid/gid setters to Unix. |
| src/deps/libkrun-sys/build.rs | Add Windows staticlib build/link path (WHPX) + make install-name fixup no-op on Windows. |
| src/deps/libgvproxy-sys/gvproxy-bridge/main.go | Add TCP listen mode (listen_addr) and adjust cleanup/accept logic for Windows. |
| src/deps/libgvproxy-sys/build.rs | Support Windows DLL import-lib workflow via LIBGVPROXY_PREBUILT. |
| src/boxlite/src/vmm/krun/context.rs | Add async start/wait/stop + console output reader and Windows net backend hook. |
| src/boxlite/src/vmm/controller/watchdog.rs | Add Windows watchdog implementation (Event + parent process handle). |
| src/boxlite/src/vmm/controller/spawn.rs | Windows: CREATE_SUSPENDED, Job Object assignment, resume threads, PID file write from parent. |
| src/boxlite/src/vmm/controller/shim.rs | Windows-aware graceful stop (event-driven waiting / termination); keep Unix SIGTERM flow. |
| src/boxlite/src/util/process.rs | Add Windows implementations for is_process_alive/kill_process and stubs for wait semantics. |
| src/boxlite/src/util/mod.rs | Disable Windows library path detection (static link) and suppress unused vars on non-Unix. |
| src/boxlite/src/util/binary_finder.rs | Support ;-separated runtime dirs and .exe lookup on Windows. |
| src/boxlite/src/system_check.rs | Add WHPX availability probing via dynamic WinHvPlatform loading + tests. |
| src/boxlite/src/runtime/signal_handler.rs | Implement Windows console control handler using SetConsoleCtrlHandler. |
| src/boxlite/src/runtime/rt_impl.rs | Use in-memory lock manager on non-Unix; adapt shutdown/force-kill logic for Windows. |
| src/boxlite/src/runtime/lock.rs | Implement Windows home-dir locking via LockFileEx. |
| src/boxlite/src/runtime/layout.rs | Skip same-filesystem validation on non-Unix. |
| src/boxlite/src/runtime/embedded.rs | Make embedded runtime path assertions separator-aware. |
| src/boxlite/src/rootfs/operations.rs | Gate Unix-only rootfs permission fixups. |
| src/boxlite/src/rootfs/mod.rs | Gate Unix-only rootfs builder/mount helpers. |
| src/boxlite/src/rootfs/guest.rs | Gate guest rootfs builder methods for Unix/Windows + avoid unused fields. |
| src/boxlite/src/portal/connection.rs | Use AF_UNIX on all platforms; add Windows AF_UNIX connector via uds_windows. |
| src/boxlite/src/net/socket_path.rs | Make socket shortening Unix-only and no-op on Windows. |
| src/boxlite/src/net/port.rs | Introduce a non-Unix TCP port allocator module (currently not wired into net). |
| src/boxlite/src/lock/mod.rs | Make file-lock manager Unix-only; keep in-memory locks available everywhere. |
| src/boxlite/src/litebox/init/types.rs | Add transport fields to init context; gate overlay/disk flags to Unix; Windows UID/GID defaults. |
| src/boxlite/src/litebox/init/tasks/vmm_spawn.rs | Normalize transports to Unix sockets on all platforms; plumb ready transport back into init ctx. |
| src/boxlite/src/litebox/init/tasks/guest_rootfs.rs | Gate guest rootfs preparation by platform; reduce unused imports. |
| src/boxlite/src/litebox/init/tasks/guest_init.rs | Clarify/adjust network init comments and imports. |
| src/boxlite/src/litebox/init/tasks/guest_connect.rs | Use transports from pipeline; add Windows AF_UNIX ready listener implementation. |
| src/boxlite/src/litebox/init/tasks/container_rootfs.rs | Gate rootfs prep paths by platform; make Windows use disk-rootfs path. |
| src/boxlite/src/litebox/box_impl.rs | Tune shutdown timing/logging for Windows WHPX and gate Unix signal usage. |
| src/boxlite/src/jailer/shim_copy.rs | Skip copying libkrunfw on Windows. |
| src/boxlite/src/jailer/sandbox/mod.rs | Add post_spawn hook; add Windows Job Object sandbox wiring. |
| src/boxlite/src/jailer/sandbox/composite.rs | Propagate post_spawn to composed sandboxes. |
| src/boxlite/src/jailer/pre_exec.rs | Mark pre-exec isolation hook Unix-only. |
| src/boxlite/src/jailer/mod.rs | Gate preserved-fds and PID pre_exec wiring to Unix; add post-spawn delegation. |
| src/boxlite/src/jailer/common/rlimit.rs | Gate rlimit utilities to Unix. |
| src/boxlite/src/jailer/common/pid.rs | Gate PID-file writer to Unix. |
| src/boxlite/src/jailer/common/mod.rs | Gate Unix-only common modules and errno helper; keep FS helpers cross-platform. |
| src/boxlite/src/jailer/common/fs.rs | Gate inode-based test to Unix. |
| src/boxlite/src/jailer/builder.rs | Gate preserved FD support to Unix. |
| src/boxlite/src/images/storage.rs | Gate layer extraction helpers to Unix where applicable. |
| src/boxlite/src/images/object.rs | Gate layer extraction path method to Unix; allow digest computation on Unix/Windows. |
| src/boxlite/src/images/mod.rs | Gate LayerExtractor re-export to Unix. |
| src/boxlite/src/images/blob_source.rs | Gate extraction logic to Unix; keep test-only helpers available. |
| src/boxlite/src/images/archive/mod.rs | Gate archive extraction submodules to Unix; keep verifier/time common. |
| src/boxlite/src/disk/mod.rs | Gate ext4 module exports to Unix/Windows; tighten re-exports under cfg. |
| src/boxlite/src/disk/ext4.rs | Improve error messages for missing e2fsprogs and normalize Windows path separators for debugfs. |
| src/boxlite/src/disk/constants.rs | Gate ext4 constants to Unix/Windows. |
| src/boxlite/src/db/migration/v6_to_v7.rs | Make path assertions separator-aware. |
| src/boxlite/src/db/boxes.rs | Cache a per-process “boot id” on non-Linux/macOS to stabilize behavior. |
| src/boxlite/src/db/base_disk.rs | Mark helper as potentially dead-code under cfg-gated callers. |
| src/boxlite/src/bin/shim/main.rs | Add Windows shutdown handlers + watchdog; keep Unix signal/pipe watchdog for Unix. |
| src/boxlite/src/bin/shim/crash_capture.rs | Add Windows unhandled exception filter capture; gate Unix signal handlers. |
| src/boxlite/Cargo.toml | Move Unix-only deps under cfg(unix); add windows-sys and uds_windows for Windows. |
| src/boxlite/build.rs | Embed kernel/initrd on Windows and add kernel/initrd discovery via BOXLITE_KERNEL_DIR. |
| sdks/python/src/lib.rs | Initialize tracing subscriber on Python module import (best-effort). |
| sdks/python/Cargo.toml | Add tracing-subscriber dependency for Python SDK. |
| scripts/build/cross-compile-kernel-windows.sh | Add kernel cross-compile script for WHPX runtime. |
| scripts/build/cross-compile-gvproxy-windows.sh | Add gvproxy DLL/import-lib cross-compile script. |
| scripts/build/cross-compile-e2fsprogs-windows.sh | Add e2fsprogs cross-compile script for Windows (mke2fs/debugfs). |
| scripts/build/build-windows-runtime.sh | Orchestrate building a full Windows runtime bundle. |
| scripts/build/build-initrd-windows.sh | Build an initrd with required modules and busybox init for WHPX. |
| docs/cross-platform-test-report-20260503.md | Add test report documenting cross-platform results and fixes. |
| Cargo.lock | Add uds_windows + windows-sys dependencies to lockfile. |
| .github/workflows/test-windows.yml | Add Windows CI compile/clippy/unit test workflow using BOXLITE_DEPS_STUB=1. |
| .github/workflows/test-windows-e2e.yml | Add manual self-hosted WHPX E2E workflow. |
| .cargo/config.toml | Add MSVC rustflag /FORCE:MULTIPLE for staticlib libkrun symbol collisions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Create a symlink (Unix) or directory junction (Windows), ignoring `AlreadyExists`. | ||
| fn symlink_or_exists(target: &Path, link: &Path, label: &str) { | ||
| match std::os::unix::fs::symlink(target, link) { | ||
| let result = { | ||
| #[cfg(unix)] | ||
| { | ||
| std::os::unix::fs::symlink(target, link) | ||
| } | ||
| #[cfg(not(unix))] | ||
| { | ||
| // Windows: use junction (works without elevated privileges, unlike symlinks) | ||
| std::os::windows::fs::symlink_dir(target, link) | ||
| } | ||
| }; |
| @@ -286,16 +298,34 @@ fn flock_exclusive(path: &Path) -> std::fs::File { | |||
| file | |||
| } | |||
|
|
|||
| /// Acquire an exclusive file lock via `LockFileEx`. | |||
| #[cfg(not(unix))] | |||
| fn flock_exclusive(path: &Path) -> std::fs::File { | |||
| // On Windows, opening with exclusive write access provides basic locking. | |||
| // For test cache warm-up serialization, this is sufficient since the file | |||
| // is held open for the duration of the warm-up. | |||
| use std::fs::OpenOptions; | |||
| OpenOptions::new() | |||
| .write(true) | |||
| .create(true) | |||
| .truncate(false) | |||
| .open(path) | |||
| .unwrap_or_else(|e| panic!("acquire lock on {}: {e}", path.display())) | |||
| } | |||
| use libkrun_sys::{ | ||
| krun_add_disk2, krun_add_net_unixgram, krun_add_net_unixstream, krun_add_virtiofs3, | ||
| krun_add_vsock, krun_add_vsock_port2, krun_create_ctx, krun_disable_implicit_vsock, | ||
| krun_free_ctx, krun_init_log, krun_set_console_output, krun_set_env, krun_set_exec, | ||
| krun_set_gpu_options, krun_set_kernel, krun_set_nested_virt, krun_set_port_map, | ||
| krun_free_ctx, krun_get_console_output, krun_init_log, krun_set_console_output, krun_set_env, | ||
| krun_set_exec, krun_set_gpu_options, krun_set_kernel, krun_set_nested_virt, krun_set_port_map, | ||
| krun_set_rlimits, krun_set_root, krun_set_root_disk_remount, krun_set_vm_config, | ||
| krun_set_workdir, krun_setgid, krun_setuid, krun_split_irqchip, krun_start_enter, | ||
| krun_set_workdir, krun_split_irqchip, krun_start, krun_start_enter, krun_stop, krun_wait, | ||
| }; |
| //! TCP port allocation for Windows transport. | ||
| //! | ||
| //! On Unix, each box gets deterministic socket paths (`box.sock`, `ready.sock`, | ||
| //! `net.sock`). On Windows, Unix sockets are unavailable — libkrun WHPX bridges | ||
| //! vsock to TCP. This module allocates ephemeral TCP ports for each box. | ||
| //! | ||
| //! # Approach | ||
| //! | ||
| //! Bind `TcpListener` to `127.0.0.1:0`, read the OS-assigned port, then drop | ||
| //! the listener. Stateless — no global registry needed. The small TOCTOU window | ||
| //! is acceptable because the ephemeral port pool is large (~16k ports). | ||
|
|
||
| #![cfg(not(unix))] | ||
|
|
||
| use boxlite_shared::errors::{BoxliteError, BoxliteResult}; | ||
| use std::net::{Ipv4Addr, SocketAddrV4, TcpListener}; |
| tracing::warn!( | ||
| "SHARED filesystem mount failed ({}), using plain directory at {}", | ||
| e, | ||
| mount_point.display() | ||
| ); | ||
| std::fs::create_dir_all(&mount_point).ok(); | ||
| Ok(()) | ||
| } |
dc2e291 to
4bd43df
Compare
4bd43df to
6f6a65d
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 84 out of 86 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Acquire an exclusive file lock via `LockFileEx`. | ||
| #[cfg(not(unix))] | ||
| fn flock_exclusive(path: &Path) -> std::fs::File { | ||
| // On Windows, opening with exclusive write access provides basic locking. | ||
| // For test cache warm-up serialization, this is sufficient since the file | ||
| // is held open for the duration of the warm-up. | ||
| use std::fs::OpenOptions; | ||
| OpenOptions::new() | ||
| .write(true) | ||
| .create(true) | ||
| .truncate(false) | ||
| .open(path) | ||
| .unwrap_or_else(|e| panic!("acquire lock on {}: {e}", path.display())) | ||
| } |
| /// Create a symlink (Unix) or directory junction (Windows), ignoring `AlreadyExists`. | ||
| fn symlink_or_exists(target: &Path, link: &Path, label: &str) { | ||
| match std::os::unix::fs::symlink(target, link) { | ||
| let result = { | ||
| #[cfg(unix)] | ||
| { | ||
| std::os::unix::fs::symlink(target, link) | ||
| } | ||
| #[cfg(not(unix))] | ||
| { | ||
| // Windows: use junction (works without elevated privileges, unlike symlinks) | ||
| std::os::windows::fs::symlink_dir(target, link) | ||
| } | ||
| }; |
| Err(e) if virtiofs.tag == mount_tags::SHARED => { | ||
| // SHARED mount is optional — on hosts without virtiofs/9p support | ||
| // (e.g., WHPX without matching kernel modules), fall back to a | ||
| // plain directory. Block devices mount into subdirs and still work. | ||
| tracing::warn!( | ||
| "SHARED filesystem mount failed ({}), using plain directory at {}", | ||
| e, | ||
| mount_point.display() | ||
| ); | ||
| std::fs::create_dir_all(&mount_point).ok(); | ||
| Ok(()) | ||
| } |
| // Skip if already mounted (check by trying to read a known entry) | ||
| let probe = match fstype { | ||
| "proc" => p.join("self").exists(), | ||
| "devtmpfs" => p.join("null").exists(), | ||
| _ => p.join(".").read_dir().is_ok_and(|mut d| d.next().is_some()), | ||
| }; | ||
| if probe { | ||
| tracing::debug!("{} already mounted, skipping", path); | ||
| continue; | ||
| } |
| // Use uds_windows in a blocking task for the accept | ||
| let accept_path = path.clone(); | ||
| race_ready_signal( | ||
| async move { | ||
| tokio::task::spawn_blocking(move || { | ||
| let listener = uds_windows::UnixListener::bind(&accept_path)?; | ||
| listener.accept().map(|_| ()) | ||
| }) | ||
| .await | ||
| .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))? | ||
| }, |
| //! TCP port allocation for Windows transport. | ||
| //! | ||
| //! On Unix, each box gets deterministic socket paths (`box.sock`, `ready.sock`, | ||
| //! `net.sock`). On Windows, Unix sockets are unavailable — libkrun WHPX bridges | ||
| //! vsock to TCP. This module allocates ephemeral TCP ports for each box. | ||
| //! | ||
| //! # Approach | ||
| //! | ||
| //! Bind `TcpListener` to `127.0.0.1:0`, read the OS-assigned port, then drop | ||
| //! the listener. Stateless — no global registry needed. The small TOCTOU window | ||
| //! is acceptable because the ephemeral port pool is large (~16k ports). |
Complete Windows native VM support using WHPX (Windows Hypervisor Platform). This enables BoxLite to run lightweight VMs on Windows without WSL2, using the same hardware-level isolation as macOS (Hypervisor.framework) and Linux (KVM). Key capabilities: - WHPX hypervisor backend with full x86_64 guest support - Multi-vCPU (up to 4) with INIT-SIPI-SIPI AP bootstrap - Userspace IOAPIC + lock-free LAPIC with ICR broadcast IPI - virtio-blk (async worker), virtio-net, virtio-vsock, virtio-rng, virtio-balloon - virtio-9p host filesystem sharing - AF_UNIX host-guest communication (replacing TCP) - gvproxy networking for full guest internet access - QCOW2 COW disk overlays - JobObject process sandbox isolation - HLT tiered sleep + LAPIC timer throttle for performance - OCI image support with ext4 disk creation Iterations completed: - Iter 1: Async Disk I/O - Iter 2: IOAPIC + LAPIC interrupt architecture - Iter 3: Multi-vCPU (2 vCPUs) - Iter 4: virtio-rng + virtio-balloon - Iter 5: HLT tiered sleep + LAPIC timer throttle - Iter 6: JobSandbox + process isolation - Iter 7: Lock-free atomic LAPIC - Iter 8: 4-vCPU fix (ICR broadcast shorthand) E2E test results: - Win11 (i5-1135G7): vm-bench 8/8, net-test 8/8 (4 vCPUs) - Win10 (i7-4770HQ): vm-bench 8/8, net-test 8/8 (4 vCPUs) - macOS/Linux: zero regression (639 unit tests pass) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
6f6a65d to
96692eb
Compare
- Add #[cfg(unix)] to verify_diff_ids() call and method definition in object.rs, since verify_tarball depends on TarballReader which is unix-only - Add #[cfg(unix)] to three verifier tests that call verify_tarball - Update libkrun submodule to include cargo fmt + DummyIrqChip fixes Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 84 out of 86 changed files in this pull request and generated 6 comments.
Comments suppressed due to low confidence (1)
src/deps/libgvproxy-sys/gvproxy-bridge/main.go:513
logrus.Infodoesn’t interpret the trailing values as structured fields; it will just print them as extra arguments. If you want key/value logging consistent with the rest of this file, uselogrus.WithFields(...).Info(...)(same applies to the "Destroyed gvproxy instance" log below).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| /// Acquire an exclusive file lock via `LockFileEx`. | ||
| #[cfg(not(unix))] | ||
| fn flock_exclusive(path: &Path) -> std::fs::File { | ||
| // On Windows, opening with exclusive write access provides basic locking. | ||
| // For test cache warm-up serialization, this is sufficient since the file | ||
| // is held open for the duration of the warm-up. | ||
| use std::fs::OpenOptions; | ||
| OpenOptions::new() | ||
| .write(true) | ||
| .create(true) | ||
| .truncate(false) | ||
| .open(path) | ||
| .unwrap_or_else(|e| panic!("acquire lock on {}: {e}", path.display())) | ||
| } |
| /// Create a symlink (Unix) or directory junction (Windows), ignoring `AlreadyExists`. | ||
| fn symlink_or_exists(target: &Path, link: &Path, label: &str) { | ||
| match std::os::unix::fs::symlink(target, link) { | ||
| let result = { | ||
| #[cfg(unix)] | ||
| { | ||
| std::os::unix::fs::symlink(target, link) | ||
| } | ||
| #[cfg(not(unix))] | ||
| { | ||
| // Windows: use junction (works without elevated privileges, unlike symlinks) | ||
| std::os::windows::fs::symlink_dir(target, link) | ||
| } | ||
| }; |
| //! TCP port allocation for Windows transport. | ||
| //! | ||
| //! On Unix, each box gets deterministic socket paths (`box.sock`, `ready.sock`, | ||
| //! `net.sock`). On Windows, Unix sockets are unavailable — libkrun WHPX bridges | ||
| //! vsock to TCP. This module allocates ephemeral TCP ports for each box. | ||
| //! | ||
| //! # Approach | ||
| //! | ||
| //! Bind `TcpListener` to `127.0.0.1:0`, read the OS-assigned port, then drop | ||
| //! the listener. Stateless — no global registry needed. The small TOCTOU window | ||
| //! is acceptable because the ephemeral port pool is large (~16k ports). | ||
|
|
||
| #![cfg(not(unix))] | ||
|
|
||
| use boxlite_shared::errors::{BoxliteError, BoxliteResult}; | ||
| use std::net::{Ipv4Addr, SocketAddrV4, TcpListener}; |
| tracing::warn!( | ||
| "SHARED filesystem mount failed ({}), using plain directory at {}", | ||
| e, | ||
| mount_point.display() | ||
| ); | ||
| std::fs::create_dir_all(&mount_point).ok(); | ||
| Ok(()) | ||
| } |
| @@ -127,9 +157,26 @@ impl<'a> ShimSpawner<'a> { | |||
| drop(stdin); // close write end — shim sees EOF | |||
| } | |||
|
|
|||
| // 9. Close read end in parent (child inherited it via fork) | |||
| // 9. Close read end in parent (child inherited it via fork on Unix) | |||
| // On Windows, ChildSetup is just a handle value — no cleanup needed. | |||
| drop(child_setup); | |||
|
|
|||
| // 10. Write PID file (Windows only). | |||
| // On Unix, the pre_exec hook writes the PID file after fork via | |||
| // async-signal-safe syscalls. On Windows, pre_exec is not available, | |||
| // so we write it from the parent after spawn succeeds. | |||
| #[cfg(windows)] | |||
| { | |||
| let pid_file = self.layout.pid_file_path(); | |||
| std::fs::write(&pid_file, child.id().to_string()).map_err(|e| { | |||
| BoxliteError::Storage(format!( | |||
| "Failed to write PID file {}: {}", | |||
| pid_file.display(), | |||
| e | |||
| )) | |||
| })?; | |||
| } | |||
| #[pymodule(name = "boxlite")] | ||
| fn boxlite_python(m: &Bound<'_, PyModule>) -> PyResult<()> { | ||
| // Initialize tracing from RUST_LOG env var (ignore if already initialized) | ||
| let _ = tracing_subscriber::fmt() | ||
| .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) | ||
| .try_init(); |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The `time` and `verifier` modules are only used by unix-gated code (LayerExtractor and verify_diff_ids). Without cfg gates, Windows CI reports dead_code and unused_imports warnings (-D warnings → errors). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 84 out of 86 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Windows: use junction (works without elevated privileges, unlike symlinks) | ||
| std::os::windows::fs::symlink_dir(target, link) | ||
| } |
| /// Acquire an exclusive file lock via `LockFileEx`. | ||
| #[cfg(not(unix))] | ||
| fn flock_exclusive(path: &Path) -> std::fs::File { | ||
| // On Windows, opening with exclusive write access provides basic locking. | ||
| // For test cache warm-up serialization, this is sufficient since the file | ||
| // is held open for the duration of the warm-up. | ||
| use std::fs::OpenOptions; | ||
| OpenOptions::new() | ||
| .write(true) | ||
| .create(true) | ||
| .truncate(false) | ||
| .open(path) | ||
| .unwrap_or_else(|e| panic!("acquire lock on {}: {e}", path.display())) | ||
| } |
| // Use uds_windows in a blocking task for the accept | ||
| let accept_path = path.clone(); | ||
| race_ready_signal( | ||
| async move { | ||
| tokio::task::spawn_blocking(move || { | ||
| let listener = uds_windows::UnixListener::bind(&accept_path)?; | ||
| listener.accept().map(|_| ()) | ||
| }) | ||
| .await | ||
| .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))? | ||
| }, |
| tracing::warn!( | ||
| "SHARED filesystem mount failed ({}), using plain directory at {}", | ||
| e, | ||
| mount_point.display() | ||
| ); | ||
| std::fs::create_dir_all(&mount_point).ok(); | ||
| Ok(()) | ||
| } |
| if let Err(e) = mount( | ||
| Some(fstype), | ||
| p, | ||
| Some(fstype), | ||
| MsFlags::empty(), | ||
| None::<&str>, | ||
| ) { | ||
| tracing::warn!("Failed to mount {} on {}: {}", fstype, path, e); | ||
| } else { | ||
| tracing::info!("Mounted {} on {}", fstype, path); | ||
| } |
| #[pymodule(name = "boxlite")] | ||
| fn boxlite_python(m: &Bound<'_, PyModule>) -> PyResult<()> { | ||
| // Initialize tracing from RUST_LOG env var (ignore if already initialized) | ||
| let _ = tracing_subscriber::fmt() | ||
| .with_env_filter(tracing_subscriber::EnvFilter::from_default_env()) | ||
| .try_init(); |
GRACEFUL_SHUTDOWN_TIMEOUT_SECS (only used by start_parent_watchdog, unix-only) and SIGNAL_EXIT_CODE_BASE (only used by crash_signal_handler, unix-only) trigger dead_code errors on Windows with -D warnings. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Split cli/terminal/mod.rs into unix.rs (existing code) + windows.rs (minimal StreamManager stub with Ctrl+C via tokio::signal::ctrl_c) - Gate integration tests that use Unix-specific APIs with #![cfg(unix)]: recovery.rs (libc::kill), copy.rs (unix::fs::symlink), mount_security.rs (unix::fs::MetadataExt), sigstop_quiesce.rs (libc::kill/SIGSTOP) The Windows CI workflow compiles all targets (--all-targets) even though it only runs unit tests, so test code must also compile on Windows. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 91 out of 93 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| #[cfg(not(unix))] | ||
| { | ||
| // Windows: use junction (works without elevated privileges, unlike symlinks) | ||
| std::os::windows::fs::symlink_dir(target, link) | ||
| } |
| // On Windows, opening with exclusive write access provides basic locking. | ||
| // For test cache warm-up serialization, this is sufficient since the file | ||
| // is held open for the duration of the warm-up. | ||
| use std::fs::OpenOptions; | ||
| OpenOptions::new() | ||
| .write(true) | ||
| .create(true) | ||
| .truncate(false) | ||
| .open(path) |
| let guest_shutdown_timeout = if cfg!(windows) { | ||
| // Short timeout: if gRPC connection is already established, the | ||
| // shutdown RPC completes in <50ms. If not (networking disabled), | ||
| // we bail quickly — the shim watchdog will exit on keepalive signal. | ||
| Duration::from_millis(200) |
| // On explicit stop, the parent (box_impl.rs) already tried Guest.Shutdown() | ||
| // RPC — skip the redundant attempt to avoid a 3s timeout when networking | ||
| // is unavailable. On parent death, we're the last chance to flush qcow2. | ||
| if !explicit_stop { | ||
| do_graceful_shutdown(transport); |
| "SHARED filesystem mount failed ({}), using plain directory at {}", | ||
| e, | ||
| mount_point.display() | ||
| ); | ||
| std::fs::create_dir_all(&mount_point).ok(); | ||
| Ok(()) |
… import - Gate test_extract_layer_preserves_whiteout_markers_for_cache with #[cfg(unix)] since extract_layer() is unix-only - Add QueryInformationJobObject to windows_sys import in job_object.rs (used in test_job_object_has_die_on_exception_and_kill_on_close and test_job_object_has_ui_restrictions) - Move #[cfg(any(linux, macos))] from assert to function level in standard_mode_enables_jailer test to avoid unused variable on Windows Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move 55 documentation files + 1 compat-check script from the main
worktree into this WHPX-feature worktree where they belong. These
are byproducts of the Windows WHPX native support iteration work
(Iter 1–8) and were authored across the WHPX development sessions.
Categories:
- WHPX iteration plans, progress reports, review reports
- Architecture / E2E test reports for Win10 / Win11
- libwkrun research + design docs (Windows-native libkrun alternative)
- VM-internals deep-dive (in-depth-{01..08}, in-depth-cn-{01..08})
- VM creation / benchmarking comparison docs
- 9p kernel support investigation (Windows VirtIO-9P alternative)
- Cross-cutting research (microvm vs QEMU, virtio protocol, market scan)
- PR-summary artifacts from prior WHPX PR drafting
No source code change. Generated docs only.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Move the WHPX-iteration slash commands from the main worktree:
- win10-{setup,sync,rebuild,test,e2e}.md
- win11-{setup,sync,rebuild,test,e2e}.md
- md-to-pdf.md (used to publish docs/*.pdf during WHPX work)
These automate the build/sync/test loop against the Win10 MBP and
Win11 T14 dev machines documented in CLAUDE.md, and the PDF export
used for review handoff.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- docs/boxlite-deps.md: dependency surface analysis written during
WHPX porting work to understand which crates would need windows-msvc
support.
- docs/{en.,}github-light.css: stylesheets used by the md-to-pdf
slash command (committed in the previous commit) to render the
WHPX docs/*.md → docs/*.pdf review artifacts in a github-light
theme.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Summary
Adds native Windows support to BoxLite using the Windows Hypervisor Platform (WHPX) API. This enables BoxLite to run lightweight Linux VMs directly on Windows without WSL2, providing the same SDK interface across all three platforms (macOS, Linux, Windows).
What works:
Depends on: boxlite-ai/libkrun#1
Architecture
Key Changes
VM Engine (
src/boxlite/src/vmm/krun/)krun_start(non-blocking) +krun_wait(poll) +krun_stop(graceful). Linux/macOS usekrun_start_enter(blocking process takeover).krun_add_virtiofs3,krun_add_net_unixgram,krun_add_vsock, etc.)Process Lifecycle (
src/boxlite/src/vmm/controller/)CREATE_SUSPENDED+ Job Object +ResumeThreadto eliminate TOCTOU. PID file written from parent.krun_stopinstead of Unix signals.Sandbox (
src/boxlite/src/jailer/)Image & Disk (
src/boxlite/src/images/)Networking
Build System
Guest Agent (
src/guest/)pidfdon Windows).CI & Scripts
.github/workflows/test-windows.yml— Windows build + unit test CI.github/workflows/test-windows-e2e.yml— Windows E2E test workflowscripts/build/build-windows-runtime.sh— Cross-compile Windows runtime depsscripts/build/cross-compile-{kernel,e2fsprogs,gvproxy}-windows.shPlatform Behavior Differences
krun_start_enter(blocking)krun_start+krun_wait(async)krun_stopAPITesting
CI (post-rebase)
cargo fmtcargo clippyManual E2E (Windows)
*Win11 BrowserBox
playwright_endpointfailure is an unrelated libcontainer issue (sendsInitReadyinstead ofIntermediateReady), not WHPX-related.Test Plan
Known Limitations
Stats
84 files changed, +6,133 / -523 (1 squashed commit)