feat(minvmd): T01 — crate scaffold, libkrun FFI wrappers, smoke test - #236
feat(minvmd): T01 — crate scaffold, libkrun FFI wrappers, smoke test#236norrietaylor wants to merge 5 commits into
Conversation
Pre-implementation baseline: the spec authored via cw-spec plus the four Gherkin features (one per demoable unit) and the local task-list-id settings. No code changes; subsequent commits implement against this spec. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…n justfile target
T01.1 — workspace skeleton only. No FFI, no runtime VM logic, no subcommands
beyond `completions`. Future sub-tasks add `src/krun/{raw,ctx}.rs` + VmError
(T01.2), the gated smoke test (T01.3), and the supervisor/boot/lifecycle
surface (T02–T04).
- crates/minvmd registered in the workspace; deps inherit via .workspace = true.
- build.rs gates link wiring on CARGO_CFG_TARGET_OS == macos; LIBKRUN_PREFIX
env overrides the default Homebrew prefix (/opt/homebrew/lib).
- minvmd.entitlements grants only com.apple.security.hypervisor.
- justfile codesign-minvmd recipe builds release and ad-hoc-signs with
`codesign --entitlements ... -s -` — reproduces the dev signing step.
- Linux build compiles to a runtime stub (R1.1); existing Linux-only CI stays
green because build.rs emits no link flags on non-macOS targets.
Refs: docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md R1.1, R1.4
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T01.2 — adds src/krun/{raw,ctx}.rs and src/error.rs. The wrappers cover the
T01 smoke-test surface (create_ctx, free_ctx, set_vm_config, set_exec,
start_enter) plus the T02 boot surface (set_root, set_kernel, add_vsock_port,
set_console_output) pulled forward to avoid a churn-only re-edit later.
- raw.rs declarations mirror libkrun.h exactly; one block-level SAFETY comment
enumerates pointer / NUL-termination / ctx_id provenance / ownership-transfer
invariants the unsafe extern block inherits from the C ABI.
- ctx.rs Context is an RAII handle: non-Clone/non-Copy, Drop calls krun_free_ctx,
start_enter consumes self via mem::forget so the freed-by-libkrun configuration
is not double-freed. Each unsafe call site (9 total) has its own SAFETY
comment naming the specific invariants the wrapper enforces.
- VmError::{Backend, NulInPath, NulInString} with hand-rolled Display +
std::error::Error impls matching the sandbox2 convention. check_backend()
preserves errno magnitude (negative → positive code) per spec lesson
"error fidelity".
- krun module is #[cfg(target_os = "macos")]-gated; portable VmError surface
compiles on both platforms. Linux build still produces only the runtime
stub — no libkrun linkage, CI stays green (R1.1).
Tests: 8 unit tests cover NUL-validation paths and check_backend semantics.
Refs: docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md R1.2, R1.3
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
T01.3 — adds tests/krun_smoke.rs plus the src/bin/krun_smoke_child.rs helper.
The helper exists as a separate binary because krun_start_enter never returns
on success — it exit()s with the guest workload's exit code, which would tear
down the test harness if called in-process. The integration test spawns the
helper via env!("CARGO_BIN_EXE_krun_smoke_child") and observes its exit code.
Gates: #[ignore] + MINVMD_E2E=1 self-skip inside the test body. Both must be
opted into before any libkrun call happens.
Two paths:
- Bring-up only: drive create_ctx → set_vm_config(1, 512) → set_exec("/bin/true")
through the safe wrappers, exit 0. Verified end-to-end against libkrun
v1.18.1 on aarch64-apple-darwin.
- Full start_enter: when MINVMD_KERNEL_PATH and MINVMD_ROOTFS_PATH are both
set, the helper additionally calls set_root + set_kernel and invokes
start_enter. Accepts libkrun's documented exit-code set
(0 / 2 / 125 / 126 / 127). T02.4 (boot_e2e) exercises this path with a
real Alpine guest + READY-marker round-trip.
Linux is unaffected: the test is target_os=macos-gated to an empty crate, and
the helper bin compiles to a stub that exits 0.
Refs: docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md R1.5
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two cosmetic cleanups from the T01 cw-review report
(docs/specs/01-spec-minvmd-host-daemon/01-review-minvmd-host-daemon.md):
- D-1: Context::start_enter no longer synthesises a misleading
`VmError::Backend { code: 0 }` when libkrun's documented "only returns on
error" contract is violated. New typed variant
`VmError::StartEnterReturnedUnexpectedly { ret }` makes the protocol
violation explicit instead of pretending it was errno 0. Added a unit
test for the Display formatting.
- D-2: set_exec envp construction unified into a single `match envp_cstrs.as_ref()`
expression that returns `(Vec<*const c_char>, *const *const c_char)`
directly, eliminating the three repeated `envp_cstrs.is_some()` checks.
Behaviour unchanged.
Also commits the cw-review report itself as the durable artifact.
Verified: cargo fmt + cargo clippy -p minvmd --all-targets -- -D warnings
clean; cargo test -p minvmd → 9 passed; gated krun_smoke against real
libkrun v1.18.1 still passes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThis PR introduces the Changesminvmd Host Daemon Scaffold and Specification
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related issues
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/specs/01-spec-minvmd-host-daemon/uds-vsock-bridge.feature (1)
42-48:⚠️ Potential issue | 🟠 Major | ⚡ Quick winFix inconsistent socket path to match spec.
The socket path
~/.local/state/minimal/minvmd/minimald.sockis inconsistent with the main specification. According to R3.1 in01-spec-minvmd-host-daemon.md, the UDS should be at:
- Primary:
$XDG_RUNTIME_DIR/minimal/minimald.sock- Fallback:
~/.minimal/local/minimald.sockThe path used here mixes XDG_STATE_HOME location (
~/.local/state) with the socket, but sockets belong in XDG_RUNTIME_DIR (or the fallback location), not in the state directory.🔧 Proposed fix
- When the host runs a client that sends "list-sessions" over the UDS at "~/.local/state/minimal/minvmd/minimald.sock" + When the host runs a client that sends "list-sessions" over the UDS at "$XDG_RUNTIME_DIR/minimal/minimald.sock"Or use the fallback path if testing without XDG_RUNTIME_DIR set:
- When the host runs a client that sends "list-sessions" over the UDS at "~/.local/state/minimal/minvmd/minimald.sock" + When the host runs a client that sends "list-sessions" over the UDS at "~/.minimal/local/minimald.sock"🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/specs/01-spec-minvmd-host-daemon/uds-vsock-bridge.feature` around lines 42 - 48, Update the UDS path in the scenario string that currently uses "~/.local/state/minimal/minvmd/minimald.sock" to match the spec: use the primary path "$XDG_RUNTIME_DIR/minimal/minimald.sock" and, where tests run without XDG_RUNTIME_DIR set, use the fallback "~/.minimal/local/minimald.sock"; locate and replace the literal socket path in the scenario text so the Given/When steps reference the spec-compliant path instead of the mixed "~/.local/state/…" path.
🧹 Nitpick comments (4)
docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md (2)
134-139: 💤 Low valueAdd language specifier to code block for better rendering.
The process model diagram is in a fenced code block without a language specifier. Adding
textorplaintextwould improve rendering and satisfy markdown linters.📝 Proposed formatting fix
-``` +```text minvmd run (parent — supervisor)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md` around lines 134 - 139, Update the fenced code block containing the process model diagram (the block showing "minvmd run", "minvmd __krun-vmm", "libkrun + Alpine VM", "minimald (pid-1)") to include a language specifier such as text or plaintext (e.g., replace ``` with ```text) so Markdown renders it correctly and markdown linters accept it.
182-187: 💤 Low valueSurround verification table with blank lines.
Markdown best practices require tables to be surrounded by blank lines for proper parsing and rendering.
📝 Proposed formatting fix
Add a blank line before line 182 (
| Check | Command |).🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md` around lines 182 - 187, The Markdown table starting with the header "| Check | Command |" needs blank lines before and after it to follow Markdown parsing best practices; edit the docs block that contains the table by inserting an empty line immediately before the line containing "| Check | Command |" and another empty line immediately after the final table row so the table is surrounded by blank lines.crates/minvmd/src/krun/ctx.rs (1)
93-96: ⚡ Quick winPrefer
Optioncombinators over enum-rebuildingmatchblocks.These matches rebuild
Optiondirectly and can be simplified with combinators for consistency.♻️ Proposed refactor
- let envp_cstrs: Option<Vec<CString>> = match envp { - Some(entries) => Some(cstrings_from_strs(entries, "envp")?), - None => None, - }; + let envp_cstrs: Option<Vec<CString>> = + envp.map(|entries| cstrings_from_strs(entries, "envp")).transpose()?; ... - let initramfs_cstr = match initramfs { - Some(p) => Some(cstring_from_path(p.as_ref(), "initramfs")?), - None => None, - }; - let cmdline_cstr = match cmdline { - Some(s) => Some(cstring_from_str(s, "cmdline")?), - None => None, - }; + let initramfs_cstr = initramfs + .map(|p| cstring_from_path(p.as_ref(), "initramfs")) + .transpose()?; + let cmdline_cstr = cmdline + .map(|s| cstring_from_str(s, "cmdline")) + .transpose()?;As per coding guidelines, "Use combinators on Option/Result (map, and_then, ok_or, unwrap_or_else, map_err) instead of match expressions that reconstruct the same enum".
Also applies to: 148-155
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/minvmd/src/krun/ctx.rs` around lines 93 - 96, The match that reconstructs envp_cstrs should be replaced with an Option combinator: call envp.map(|entries| cstrings_from_strs(entries, "envp")).transpose()/map(|e| ... ) as appropriate so you don't rebuild the Option via match; update the analogous match at lines 148-155 (e.g., argv_cstrs / cstrings_from_strs usage) to use the same Option combinator pattern (map/and_then/transpose) to simplify and follow the guideline.crates/minvmd/src/bin/krun_smoke_child.rs (1)
44-44: ⚡ Quick winUse captured identifier formatting for consistency.
Switch to captured formatting to match the Rust style guideline.
✏️ Proposed change
- eprintln!("STAGE: create_ctx ok ctx_id={}", ctx.id()); + eprintln!("STAGE: create_ctx ok ctx_id={}", ctx.id());// Better: eprintln!("STAGE: create_ctx ok ctx_id={ctx_id}");As per coding guidelines, "Use captured-identifier formatting in format strings:
format!("{path}")overformat!("{}", path)".🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@crates/minvmd/src/bin/krun_smoke_child.rs` at line 44, The eprintln uses positional formatting with ctx.id(); instead, bind the id to a local identifier and use captured-identifier formatting: create a let ctx_id = ctx.id(); (referencing ctx.id()) and change the eprintln call to use the captured {ctx_id} in the format string (eprintln!("STAGE: create_ctx ok ctx_id={ctx_id}")), keeping the same message and using the existing ctx and eprintln symbols.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@docs/specs/01-spec-minvmd-host-daemon/uds-vsock-bridge.feature`:
- Around line 42-48: Update the UDS path in the scenario string that currently
uses "~/.local/state/minimal/minvmd/minimald.sock" to match the spec: use the
primary path "$XDG_RUNTIME_DIR/minimal/minimald.sock" and, where tests run
without XDG_RUNTIME_DIR set, use the fallback "~/.minimal/local/minimald.sock";
locate and replace the literal socket path in the scenario text so the
Given/When steps reference the spec-compliant path instead of the mixed
"~/.local/state/…" path.
---
Nitpick comments:
In `@crates/minvmd/src/bin/krun_smoke_child.rs`:
- Line 44: The eprintln uses positional formatting with ctx.id(); instead, bind
the id to a local identifier and use captured-identifier formatting: create a
let ctx_id = ctx.id(); (referencing ctx.id()) and change the eprintln call to
use the captured {ctx_id} in the format string (eprintln!("STAGE: create_ctx ok
ctx_id={ctx_id}")), keeping the same message and using the existing ctx and
eprintln symbols.
In `@crates/minvmd/src/krun/ctx.rs`:
- Around line 93-96: The match that reconstructs envp_cstrs should be replaced
with an Option combinator: call envp.map(|entries| cstrings_from_strs(entries,
"envp")).transpose()/map(|e| ... ) as appropriate so you don't rebuild the
Option via match; update the analogous match at lines 148-155 (e.g., argv_cstrs
/ cstrings_from_strs usage) to use the same Option combinator pattern
(map/and_then/transpose) to simplify and follow the guideline.
In `@docs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.md`:
- Around line 134-139: Update the fenced code block containing the process model
diagram (the block showing "minvmd run", "minvmd __krun-vmm", "libkrun + Alpine
VM", "minimald (pid-1)") to include a language specifier such as text or
plaintext (e.g., replace ``` with ```text) so Markdown renders it correctly and
markdown linters accept it.
- Around line 182-187: The Markdown table starting with the header "| Check |
Command |" needs blank lines before and after it to follow Markdown parsing best
practices; edit the docs block that contains the table by inserting an empty
line immediately before the line containing "| Check | Command |" and another
empty line immediately after the final table row so the table is surrounded by
blank lines.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3985049a-0b99-45e6-997d-8fa51a09263f
⛔ Files ignored due to path filters (1)
Cargo.lockis excluded by!**/*.lock
📒 Files selected for processing (26)
.claude/settings.local.jsonCargo.tomlcrates/minvmd/Cargo.tomlcrates/minvmd/build.rscrates/minvmd/minvmd.entitlementscrates/minvmd/src/bin/krun_smoke_child.rscrates/minvmd/src/error.rscrates/minvmd/src/krun/ctx.rscrates/minvmd/src/krun/mod.rscrates/minvmd/src/krun/raw.rscrates/minvmd/src/lib.rscrates/minvmd/src/main.rscrates/minvmd/tests/krun_smoke.rsdocs/specs/01-spec-minvmd-host-daemon/01-proofs/T01.1-01-cli.txtdocs/specs/01-spec-minvmd-host-daemon/01-proofs/T01.1-proofs.mddocs/specs/01-spec-minvmd-host-daemon/01-proofs/T01.2-01-file.txtdocs/specs/01-spec-minvmd-host-daemon/01-proofs/T01.2-proofs.mddocs/specs/01-spec-minvmd-host-daemon/01-proofs/T01.3-01-cli.txtdocs/specs/01-spec-minvmd-host-daemon/01-proofs/T01.3-proofs.mddocs/specs/01-spec-minvmd-host-daemon/01-review-minvmd-host-daemon.mddocs/specs/01-spec-minvmd-host-daemon/01-spec-minvmd-host-daemon.mddocs/specs/01-spec-minvmd-host-daemon/crate-scaffold-ffi-wrappers-libkrun-smoke-test.featuredocs/specs/01-spec-minvmd-host-daemon/lifecycle-daemon-auto-spawn-status-stop.featuredocs/specs/01-spec-minvmd-host-daemon/uds-vsock-bridge.featuredocs/specs/01-spec-minvmd-host-daemon/vm-bring-up-with-virtio-linux-kernel-and-alpine-rootfs.featurejustfile
Summary
Lands the scaffold for
minvmdcrates/minvmdworkspace member; macOS-onlykrunmodule gated via#[cfg(target_os = "macos")]so existing Linux-only CI stays green (R1.1).src/krun/raw.rs(T01 + T02 surface pulled forward to avoid churn) with a single block-level// SAFETY:enumerating C-ABI invariants; safe wrappers insrc/krun/ctx.rswith per-call SAFETY comments (9 unsafe blocks, 9 SAFETY comments).Contexthandle: non-Clone/non-Copy,Dropcallskrun_free_ctx,start_enterconsumes viamem::forget(libkrun docs: configuration is consumed unconditionally → no double-free).VmErrormatching thesandbox2convention (hand-rolledDisplay+std::error::Error, nothiserror).Backend { op, code }preserves errno magnitude per spec lesson error fidelity; dedicatedStartEnterReturnedUnexpectedly { ret }variant for the libkrun-docs-violating path.minvmd.entitlementsgrants onlycom.apple.security.hypervisor;justfilecodesign-minvmdrecipe builds release + ad-hoc-signs.tests/krun_smoke.rs+ helper binsrc/bin/krun_smoke_child.rs—#[ignore]+MINVMD_E2E=1guard. Verified end-to-end against libkrun v1.18.1 onaarch64-apple-darwinOut of scope (intentional)
minimaldminimal2)\Cargo.lockis up-to-date #160; not in v0.1.krun_set_kernel,krun_set_root,krun_add_vsock_port,krun_set_console_output) is declared and wrapped but not exercised by this PR — will wire it up in subsequent PRTest plan
cargo build -p minvmd— greencargo clippy -p minvmd --all-targets -- -D warnings— no issuescargo fmt --check— cleancargo test -p minvmd— 9 passed, 1 ignored (the gated smoke test)MINVMD_E2E=1 cargo test -p minvmd --test krun_smoke -- --include-ignored— 1 passed against real libkrun v1.18.1 on macOSkrunmodule gated out, no libkrun linkage attempted)just codesign-minvmdon a Mac dev box — produces a release binary signed with the hypervisor entitlementCommits
3928bbeddocs(minvmd): land 01-spec-minvmd-host-daemon and BDD features9e4799b9feat(minvmd): scaffold crate with build.rs, entitlements, and codesign justfile target04ad1068feat(minvmd): libkrun FFI bindings with safe wrappers and typed VmErrord08671cctest(minvmd): gated krun_smoke FFI bring-up testb05e4918refactor(minvmd): address cw-review advisories on ctx.rs🤖 Generated with Claude Code